# This file contains the description of all configuration parameters # It may be included only by batch files, never in portal or handler chain # for performances reasons # DON'T FORGET TO RUN "make json" AFTER EACH CHANGE package Lemonldap::NG::Manager::Build::Attributes; our $VERSION = '2.0.15'; use strict; use Regexp::Common qw/URI/; sub perlExpr { my ( $val, $conf ) = @_; my $cpt = new Safe; $cpt->share_from( 'MIME::Base64', ['&encode_base64'] ); $cpt->share_from( 'Lemonldap::NG::Handler::Main::Jail', [ '&encrypt', '&token', @Lemonldap::NG::Handler::Main::Jail::builtCustomFunctions ] ); $cpt->share_from( 'Lemonldap::NG::Common::Safelib', $Lemonldap::NG::Common::Safelib::functions ); $cpt->reval("BEGIN { 'warnings'->unimport; } $val"); my $err = join( '', grep { $_ =~ /(?:Undefined subroutine|Devel::StackTrace)/ ? () : $_ } split( /\n/, $@ ) ); return ( -1, "__badExpression__: $err" ) if ( $err && $conf->{useSafeJail} ); return ( $val =~ qr/(?<=[^=\|\?])=(?![>=~])/ && $conf->{avoidAssignment} ) ? ( 1, "__badExpressionAssignment__" ) : 1; } my $url_re = $RE{URI}{HTTP}{ -scheme => "https?" }; $url_re =~ s/(?<=[^\\])\$/\\\$/g; my $url = qr/$url_re/; my $urlOrEmpty = qr/(?:^$|$url_re)/; sub types { return { # Simple text types text => { test => sub { 1 }, msgFail => '__malformedValue__', }, password => { test => sub { 1 }, msgFail => '__malformedValue__', }, longtext => { test => sub { 1 } }, url => { form => 'text', test => $urlOrEmpty, msgFail => '__badUrl__', }, PerlModule => { form => 'text', test => qr/^(?:[a-zA-Z][a-zA-Z0-9]*)*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/, msgFail => '__badPerlPackageName__', }, hostname => { form => 'text', test => qr/^(?:$Regexp::Common::URI::RFC2396::host)?$/, msgFail => '__badHostname__', }, pcre => { form => 'text', test => sub { eval { qr/$_[0]/ }; return $@ ? ( 0, "__badRegexp__: $@" ) : (1); }, }, lmAttrOrMacro => { form => 'text', test => sub { my ( $val, $conf ) = @_; return 1 if ( defined $conf->{macros}->{$val} or $val =~ m/^_/ ); foreach ( keys %$conf ) { return 1 if ( $_ =~ /exportedvars$/i and defined $conf->{$_}->{$val} ); } return ( 1, "__unknownAttrOrMacro__: $val" ); }, }, # Other types int => { test => qr/^\-?\d+$/, msgFail => '__notAnInteger__', }, bool => { test => qr/^[01]$/, msgFail => '__notABoolean__', }, trool => { test => qr/^(?:-1|0|1)$/, msgFail => '__authorizedValues__: -1, 0, 1', }, boolOrExpr => { test => sub { return perlExpr(@_) }, msgFail => '__notAValidPerlExpression__', }, keyTextContainer => { test => qr/./, msgFail => '__emptyValueNotAllowed__', keyTest => qr/^\w[\w\.\-]*$/, keyMsgFail => '__badKeyName__', }, subContainer => { keyTest => qr/\w/, test => sub { 1 }, }, select => { test => sub { return ( 0, "Value is not a scalar" ) if ref( $_[0] ); my $test = grep ( { $_ eq $_[0] } map ( { $_->{k} } @{ $_[2]->{select} } ) ); return $test ? 1 : ( 1, "Invalid value '$_[0]' for this select" ); }, }, # Files type (long text) file => { test => sub { 1 } }, RSAPublicKey => { test => sub { return ( $_[0] =~ /^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); }, }, 'RSAPublicKeyOrCertificate' => { 'test' => sub { return ( $_[0] =~ /^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); }, }, RSAPrivateKey => { test => sub { return ( $_[0] =~ /^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); }, }, authParamsText => { test => sub { 1 } }, blackWhiteList => { test => sub { 1 } }, catAndAppList => { test => sub { 1 } }, keyText => { keyTest => qr/^[a-zA-Z0-9_]+$/, test => qr/^.*$/, msgFail => '__badValue__', }, menuApp => { test => sub { 1 } }, menuCat => { test => sub { 1 } }, oidcAttribute => { test => sub { 1 } }, oidcOPMetaDataNode => { test => sub { 1 } }, oidcRPMetaDataNode => { test => sub { 1 } }, oidcmetadatajson => { test => sub { 1 } }, oidcmetadatajwks => { test => sub { 1 } }, portalskin => { test => sub { 1 } }, portalskinbackground => { test => sub { 1 } }, post => { test => sub { 1 } }, rule => { test => sub { 1 } }, samlAssertion => { test => sub { 1 } }, samlAttribute => { test => sub { 1 } }, samlIDPMetaDataNode => { test => sub { 1 } }, samlSPMetaDataNode => { test => sub { 1 } }, samlService => { test => sub { 1 } }, array => { test => sub { 1 } }, }; } sub attributes { return { # Other checkTime => { type => 'int', documentation => 'Timeout to check new configuration in local cache', default => 600, flags => 'hp', }, mySessionAuthorizedRWKeys => { type => 'array', documentation => 'Alterable session keys by user itself', default => [ '_appsListOrder', '_oidcConnectedRP', '_oidcConsents' ], }, configStorage => { type => 'text', documentation => 'Configuration storage', flags => 'hmp', }, localStorage => { type => 'text', documentation => 'Local cache', flags => 'hmp', }, localStorageOptions => { type => 'keyTextContainer', documentation => 'Local cache parameters', flags => 'hmp', }, cfgNum => { type => 'int', default => 0, documentation => 'Enable Cross Domain Authentication', }, cfgAuthor => { type => 'text', documentation => 'Name of the author of the current configuration', }, cfgAuthorIP => { type => 'text', documentation => 'Uploader IP address of the current configuration', }, cfgDate => { type => 'int', documentation => 'Timestamp of the current configuration', }, cfgLog => { type => 'longtext', documentation => 'Configuration update log', }, cfgVersion => { type => 'text', documentation => 'Version of LLNG which build configuration', }, status => { type => 'bool', documentation => 'Status daemon activation', flags => 'h', }, confirmFormMethod => { type => "select", select => [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], default => 'post', documentation => 'HTTP method for confirm page form', }, customFunctions => { type => 'text', test => qr/^(?:\w+(?:::\w+)*(?:\s+\w+(?:::\w+)*)*)?$/, help => 'customfunctions.html', msgFail => "__badCustomFuncName__", documentation => 'List of custom functions', flags => 'hmp', }, https => { default => -1, type => 'trool', documentation => 'Use HTTPS for redirection from portal', flags => 'h', }, infoFormMethod => { type => "select", select => [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], default => 'get', documentation => 'HTTP method for info page form', }, port => { default => -1, type => 'int', documentation => 'Force port in redirection', flags => 'h', }, jsRedirect => { type => 'boolOrExpr', default => 0, documentation => 'Use javascript for redirections', }, logoutServices => { type => 'keyTextContainer', help => 'logoutforward.html', default => {}, documentation => 'Send logout trough GET request to these services', }, maintenance => { default => 0, type => 'bool', documentation => 'Maintenance mode for all virtual hosts', flags => 'h', }, nginxCustomHandlers => { type => 'keyTextContainer', keyTest => qr/^\w+$/, test => qr/^[a-zA-Z][a-zA-Z0-9]*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/, help => 'handlerarch.html', msgFail => '__badPerlPackageName__', documentation => 'Custom Nginx handler (deprecated)', }, noAjaxHook => { default => 0, type => 'bool', documentation => 'Avoid replacing 302 by 401 for Ajax responses', }, portal => { type => 'url', default => 'http://auth.example.com/', documentation => 'Portal URL', flags => 'hmp', test => $url, msgFail => '__badUrl__', }, portalFavicon => { type => 'text', default => 'common/favicon.ico', documentation => 'Path to favicon file', }, portalCustomCss => { type => 'text', documentation => 'Path to custom CSS file', }, portalStatus => { type => 'bool', default => 0, help => 'status.html', documentation => 'Enable portal status', }, portalUserAttr => { type => 'text', default => '_user', documentation => 'Session parameter to display connected user in portal', }, redirectFormMethod => { type => "select", select => [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], default => 'get', documentation => 'HTTP method for redirect page form', }, reloadTimeout => { type => 'int', default => 5, documentation => 'Configuration reload timeout', flags => 'm', }, reloadUrls => { type => 'keyTextContainer', help => 'configlocation.html#configuration-reload', keyTest => qr/^$Regexp::Common::URI::RFC2396::host(?::\d+)?$/, test => $url, msgFail => '__badUrl__', documentation => 'URL to call on reload', }, compactConf => { type => 'bool', default => 0, documentation => 'Compact configuration', }, portalMainLogo => { type => 'text', default => 'common/logos/logo_llng_400px.png', documentation => 'Portal main logo path', }, showLanguages => { type => 'bool', default => 1, documentation => 'Display langs icons', }, scrollTop => { type => 'int', default => 400, documentation => 'Display back to top button', }, staticPrefix => { type => 'text', documentation => 'Prefix of static files for HTML templates', }, groupsBeforeMacros => { type => 'bool', default => 0, documentation => 'Compute groups before macros', }, multiValuesSeparator => { type => 'authParamsText', default => '; ', documentation => 'Separator for multiple values', flags => 'hmp', }, rememberAuthChoiceRule => { type => 'boolOrExpr', default => 0, documentation => 'remember auth choice activation rule', }, rememberCookieName => { type => 'text', test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/, msgFail => '__badCookieName__', default => 'llngrememberauthchoice', documentation => 'Name of the remember auth choice cookie', flags => 'p', }, rememberCookieTimeout => { type => 'int', default => 31536000, documentation => 'lifetime of the remember auth choice cookie', flags => 'm', }, rememberDefaultChecked => { type => 'bool', default => 0, documentation => 'Is remember auth choice checkbox enabled by default?', }, rememberTimer => { type => 'int', default => 5, documentation => 'timer before automatic authentication with remembered choice', flags => 'm', }, stayConnected => { type => 'boolOrExpr', default => 0, documentation => 'Stay connected activation rule', }, stayConnectedBypassFG => { type => 'bool', default => 0, documentation => 'Disable fingerprint checkng', }, 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, documentation => 'Enable CheckState plugin', }, checkStateSecret => { type => 'text', documentation => 'Secret token for CheckState plugin', }, checkDevOps => { default => 0, type => 'bool', documentation => 'Enable check DevOps', flags => 'p', }, checkDevOpsDownload => { default => 1, type => 'bool', documentation => 'Enable check DevOps download field', flags => 'p', }, checkDevOpsDisplayNormalizedHeaders => { default => 1, type => 'bool', documentation => 'Display normalized headers', flags => 'p', }, checkDevOpsCheckSessionAttributes => { default => 1, type => 'bool', documentation => 'Check if session attributes exist', flags => 'p', }, checkUser => { default => 0, type => 'bool', documentation => 'Enable check user', flags => 'p', }, checkUserIdRule => { type => 'text', test => sub { return perlExpr(@_) }, default => 1, documentation => 'checkUser identities rule', }, checkUserUnrestrictedUsersRule => { type => 'text', test => sub { return perlExpr(@_) }, documentation => 'checkUser unrestricted users rule', flags => 'p', }, checkUserHiddenAttributes => { type => 'text', default => '_loginHistory, _session_id, hGroups', documentation => 'Attributes to hide in CheckUser plugin', flags => 'p', }, checkUserSearchAttributes => { type => 'text', documentation => 'Attributes used for retrieving sessions in user DataBase', flags => 'p', }, checkUserDisplayPersistentInfo => { default => 0, type => 'boolOrExpr', documentation => 'Display persistent session info rule', flags => 'p', }, checkUserDisplayEmptyValues => { default => 0, type => 'boolOrExpr', documentation => 'Display session empty values rule', flags => 'p', }, checkUserDisplayEmptyHeaders => { default => 0, type => 'boolOrExpr', documentation => 'Display empty headers rule', flags => 'p', }, checkUserDisplayNormalizedHeaders => { default => 0, type => 'boolOrExpr', documentation => 'Display normalized headers rule', flags => 'p', }, checkUserDisplayComputedSession => { default => 1, type => 'boolOrExpr', documentation => 'Display empty headers rule', flags => 'p', }, checkUserDisplayHistory => { default => 0, type => 'boolOrExpr', documentation => 'Display history rule', flags => 'p', }, checkUserDisplayHiddenAttributes => { default => 0, type => 'boolOrExpr', documentation => 'Display hidden attributes rule', flags => 'p', }, checkUserHiddenHeaders => { type => 'keyTextContainer', keyTest => qr/^\S+$/, keyMsgFail => '__badHostname__', test => { keyTest => qr/^(?=[^\-])[\w\-\s]+(?<=[^-])$/, keyMsgFail => '__badHeaderName__', test => sub { return perlExpr(@_) }, }, documentation => 'Header values to hide if not empty', }, findUser => { default => 0, type => 'bool', documentation => 'Enable find user', flags => 'p', }, findUserSearchingAttributes => { type => 'keyTextContainer', keyTest => qr/^\S+$/, documentation => 'Attributes used for searching accounts', }, findUserExcludingAttributes => { type => 'keyTextContainer', keyTest => qr/^\S+$/, documentation => 'Attributes used for excluding accounts', }, findUserWildcard => { type => 'text', default => '*', documentation => 'Character used as wildcard', }, findUserControl => { type => 'pcre', default => '^[*\w]+$', documentation => 'Regular expression to validate parameters', }, newLocationWarning => { default => 0, type => 'bool', documentation => 'Enable New Location Warning', }, newLocationWarningLocationAttribute => { type => 'text', default => 'ipAddr', documentation => 'New location session attribute', }, newLocationWarningLocationDisplayAttribute => { type => 'text', default => '', documentation => 'New location session attribute for user display', }, newLocationWarningMaxValues => { type => 'int', default => '0', documentation => 'How many previous locations should be compared', }, newLocationWarningMailAttribute => { type => 'text', documentation => 'New location warning mail session attribute', }, newLocationWarningMailBody => { type => 'longtext', documentation => 'Mail body for new location warning', }, newLocationWarningMailSubject => { type => 'text', documentation => 'Mail subject for new location warning', }, globalLogoutRule => { type => 'boolOrExpr', default => 0, documentation => 'Global logout activation rule', flags => 'p', }, globalLogoutTimer => { default => 1, type => 'bool', documentation => 'Global logout auto accept time', flags => 'p', }, globalLogoutCustomParam => { type => 'text', documentation => 'Custom session parameter to display', flags => 'p', }, impersonationMergeSSOgroups => { default => 0, type => 'boolOrExpr', documentation => 'Merge spoofed and real SSO groups', flags => 'p', }, impersonationPrefix => { type => 'text', default => 'real_', documentation => 'Prefix to rename real session attributes', flags => 'p', }, impersonationRule => { type => 'boolOrExpr', default => 0, documentation => 'Impersonation activation rule', flags => 'p', }, impersonationIdRule => { type => 'text', test => sub { return perlExpr(@_) }, default => 1, documentation => 'Impersonation identities rule', flags => 'p', }, impersonationUnrestrictedUsersRule => { type => 'text', test => sub { return perlExpr(@_) }, documentation => 'Impersonation unrestricted users rule', flags => 'p', }, impersonationHiddenAttributes => { type => 'text', default => '_2fDevices, _loginHistory', documentation => 'Attributes to skip', flags => 'p', }, impersonationSkipEmptyValues => { default => 1, type => 'bool', documentation => 'Skip session empty values', flags => 'p', }, contextSwitchingRule => { type => 'boolOrExpr', default => 0, documentation => 'Context switching activation rule', flags => 'p', }, contextSwitchingIdRule => { type => 'text', test => sub { return perlExpr(@_) }, default => 1, documentation => 'Context switching identities rule', flags => 'p', }, contextSwitchingUnrestrictedUsersRule => { type => 'text', test => sub { return perlExpr(@_) }, documentation => 'Context switching unrestricted users rule', flags => 'p', }, contextSwitchingStopWithLogout => { type => 'bool', default => 1, documentation => 'Stop context switching by logout', flags => 'p', }, contextSwitchingAllowed2fModifications => { type => 'bool', default => 0, documentation => 'Allowed SFA modifications', flags => 'p', }, contextSwitchingPrefix => { type => 'text', default => 'switching', documentation => 'Prefix to store real session Id', flags => 'p', }, decryptValueRule => { type => 'boolOrExpr', default => 0, documentation => 'Decrypt value activation rule', flags => 'p', }, decryptValueFunctions => { type => 'text', test => qr/^(?:\w+(?:::\w+)*(?:\s+\w+(?:::\w+)*)*)?$/, msgFail => "__badCustomFuncName__", documentation => 'Custom function used for decrypting values', flags => 'p', }, skipRenewConfirmation => { type => 'bool', default => 0, documentation => 'Avoid asking confirmation when an Issuer asks to renew auth', }, skipUpgradeConfirmation => { type => 'bool', default => 0, documentation => 'Avoid asking confirmation during a session upgrade', }, refreshSessions => { type => 'bool', documentation => 'Refresh sessions plugin', }, forceGlobalStorageIssuerOTT => { type => 'bool', documentation => 'Force Issuer tokens to be stored into Global Storage', }, handlerInternalCache => { type => 'int', default => 15, documentation => 'Handler internal cache timeout', flags => 'hp', }, handlerServiceTokenTTL => { type => 'int', default => 30, documentation => 'Handler ServiceToken timeout', flags => 'hp', }, # Loggers (ini only) logLevel => { type => 'text', documentation => 'Log level, must be set in .ini', flags => 'hmp', }, logger => { type => 'text', documentation => 'technical logger', flags => 'hmp', }, userLogger => { type => 'text', documentation => 'User actions logger', flags => 'hmp', }, log4perlConfFile => { type => 'text', documentation => 'Log4Perl logger configuration file', flags => 'hmp', }, sentryDsn => { type => 'text', documentation => 'Sentry logger DSN', flags => 'hmp', }, syslogFacility => { type => 'text', documentation => 'Syslog logger technical facility', flags => 'hmp', }, userSyslogFacility => { type => 'text', documentation => 'Syslog logger user-actions facility', flags => 'hmp', }, # Manager or PSGI protected apps protection => { type => 'text', test => qr/^(?:none|authenticate|manager|)$/, msgFail => '__authorizedValues__: none authenticate manager', documentation => 'Manager protection method', flags => 'hm', }, # Menu activeTimer => { type => 'bool', default => 1, documentation => 'Enable timers on portal pages', }, applicationList => { type => 'catAndAppList', keyTest => qr/\w/, help => 'portalmenu.html#categories-and-applications', default => { default => { catname => 'Default category', type => "category" } }, documentation => 'Applications list', }, portalErrorOnExpiredSession => { type => 'bool', default => 1, documentation => 'Show error if session is expired', }, portalErrorOnMailNotFound => { type => 'bool', default => 0, documentation => 'Show error if mail is not found in password reset process', }, portalOpenLinkInNewWindow => { type => 'bool', default => 0, documentation => 'Open applications in new windows', }, portalPingInterval => { type => 'int', default => 60000, documentation => 'Interval in ms between portal Ajax pings ', }, portalSkin => { type => 'portalskin', default => 'bootstrap', documentation => 'Name of portal skin', select => [ { k => 'bootstrap', v => 'Bootstrap' }, ], }, portalSkinBackground => { type => 'portalskinbackground', documentation => 'Background image of portal skin', select => [ { k => "", v => 'None' }, { k => "1280px-Anse_Source_d'Argent_2-La_Digue.jpg", v => 'Anse' }, { k => "1280px-Autumn-clear-water-waterfall-landscape_-_Virginia_-_ForestWander.jpg", v => 'Waterfall' }, { k => "1280px-BrockenSnowedTrees.jpg", v => 'Snowed Trees' }, { k => "1280px-Cedar_Breaks_National_Monument_partially.jpg", v => 'National Monument' }, { k => "1280px-Parry_Peak_from_Winter_Park.jpg", v => 'Winter' }, { k => "Aletschgletscher_mit_Pinus_cembra1.jpg", v => 'Pinus' }, ], }, portalSkinRules => { type => 'keyTextContainer', help => 'portalcustom.html', keyTest => sub { return perlExpr(@_) }, keyMsgFail => '__badSkinRule__', test => qr/^\w+$/, msgFail => '__badValue__', documentation => 'Rules to choose portal skin', }, # Security formTimeout => { default => 120, type => 'int', documentation => 'Token timeout for forms', }, issuersTimeout => { default => 120, type => 'int', documentation => 'Token timeout for issuers', }, requireToken => { default => 1, type => 'boolOrExpr', documentation => 'Enable token for forms', }, tokenUseGlobalStorage => { default => 0, type => 'bool', documentation => 'Enable global token storage', }, cda => { default => 0, type => 'bool', documentation => 'Enable Cross Domain Authentication', flags => 'hp', }, checkXSS => { default => 1, type => 'bool', documentation => 'Check XSS', }, portalForceAuthn => { default => 0, help => 'forcereauthn.html', type => 'bool', documentation => 'Enable force to authenticate when displaying portal', }, portalForceAuthnInterval => { default => 5, type => 'int', documentation => 'Maximum interval in seconds since last authentication to force reauthentication', }, bruteForceProtection => { default => 0, help => 'bruteforceprotection.html', type => 'bool', documentation => 'Enable brute force attack protection', }, bruteForceProtectionTempo => { default => 30, type => 'int', documentation => 'Lock time', }, bruteForceProtectionMaxAge => { default => 300, type => 'int', documentation => 'Max age between current and first failed login', }, bruteForceProtectionMaxFailed => { default => 3, type => 'int', documentation => 'Max allowed failed login', }, bruteForceProtectionMaxLockTime => { default => 900, type => 'int', documentation => 'Max lock time', }, bruteForceProtectionIncrementalTempo => { default => 0, help => 'bruteforceprotection.html', type => 'bool', documentation => 'Enable incremental lock time for brute force attack protection', }, bruteForceProtectionLockTimes => { type => 'text', default => '15, 30, 60, 300, 600', documentation => 'Incremental lock time values for brute force attack protection', }, grantSessionRules => { type => 'grantContainer', keyTest => sub { return perlExpr(@_) }, test => sub { 1 }, documentation => 'Rules to grant sessions', default => {}, }, hiddenAttributes => { type => 'text', default => '_password, _2fDevices', documentation => 'Name of attributes to hide in logs', }, displaySessionId => { type => 'bool', default => 1, documentation => 'Display _session_id with sessions explorer', }, persistentSessionAttributes => { type => 'text', default => '_loginHistory _2fDevices notification_', documentation => 'Persistent session attributes to hide', }, key => { type => 'password', documentation => 'Secret key', }, corsEnabled => { default => 1, type => 'bool', documentation => 'Enable Cross-Origin Resource Sharing', }, corsAllow_Credentials => { type => 'text', default => 'true', documentation => 'Allow credentials for Cross-Origin Resource Sharing', }, corsAllow_Headers => { type => 'text', default => '*', documentation => 'Allowed headers for Cross-Origin Resource Sharing', }, corsAllow_Methods => { type => 'text', default => 'POST,GET', documentation => 'Allowed methods for Cross-Origin Resource Sharing', }, corsAllow_Origin => { type => 'text', default => '*', documentation => 'Allowed origine for Cross-Origin Resource Sharing', }, corsExpose_Headers => { type => 'text', default => '*', documentation => 'Exposed headers for Cross-Origin Resource Sharing', }, corsMax_Age => { type => 'text', default => '86400', # 24 hours documentation => 'Max-age for Cross-Origin Resource Sharing', }, strictTransportSecurityMax_Age => { type => 'text', documentation => 'Max-age for Strict-Transport-Security', }, cspDefault => { type => 'text', default => "'self'", documentation => 'Default value for Content-Security-Policy', }, cspFormAction => { type => 'text', default => "*", documentation => 'Form action destination for Content-Security-Policy', }, cspImg => { type => 'text', default => "'self' data:", documentation => 'Image source for Content-Security-Policy', }, cspScript => { type => 'text', default => "'self'", documentation => 'Javascript source for Content-Security-Policy', }, cspStyle => { type => 'text', default => "'self'", documentation => 'Style source for Content-Security-Policy', }, cspConnect => { type => 'text', default => "'self'", documentation => 'Authorized Ajax destination for Content-Security-Policy', }, cspFont => { type => 'text', default => "'self'", documentation => 'Font source for Content-Security-Policy', }, cspFrameAncestors => { type => 'text', default => '', documentation => 'Frame-Ancestors for Content-Security-Policy', }, portalAntiFrame => { default => 1, type => 'bool', documentation => 'Avoid portal to be displayed inside frames', }, portalCheckLogins => { default => 1, type => 'bool', documentation => 'Display login history checkbox in portal', }, randomPasswordRegexp => { type => 'pcre', default => '[A-Z]{3}[a-z]{5}.\d{2}', documentation => 'Regular expression to create a random password', }, trustedDomains => { type => 'text', documentation => 'Trusted domains', }, storePassword => { default => 0, type => 'bool', documentation => 'Store password in session', }, timeout => { type => 'int', test => sub { $_[0] > 0 }, default => 72000, documentation => 'Session timeout on server side', }, timeoutActivity => { type => 'int', test => sub { $_[0] >= 0 }, default => 0, documentation => 'Session activity timeout on server side', }, timeoutActivityInterval => { type => 'int', test => sub { $_[0] >= 0 }, default => 60, documentation => 'Update session timeout interval on server side', }, userControl => { type => 'pcre', default => '^[\w\.\-@]+$', documentation => 'Regular expression to validate login', }, browsersDontStorePassword => { default => 0, type => 'bool', documentation => 'Avoid browsers to store users password', }, useRedirectOnError => { type => 'bool', default => 1, documentation => 'Use 302 redirect code for error (500)', flags => 'h', }, useRedirectOnForbidden => { default => 0, type => 'bool', documentation => 'Use 302 redirect code for forbidden (403)', }, useSafeJail => { default => 1, type => 'bool', help => 'safejail.html', documentation => 'Activate Safe jail', flags => 'hp', }, avoidAssignment => { default => 0, type => 'bool', help => 'safejail.html', documentation => 'Avoid assignment in expressions', flags => 'hp', }, whatToTrace => { type => 'lmAttrOrMacro', default => 'uid', documentation => 'Session parameter used to fill REMOTE_USER', flags => 'hp', }, customToTrace => { type => 'lmAttrOrMacro', documentation => 'Session parameter used to fill REMOTE_CUSTOM', flags => 'hp', }, lwpOpts => { type => 'keyTextContainer', documentation => 'Options passed to LWP::UserAgent', }, lwpSslOpts => { type => 'keyTextContainer', documentation => 'SSL options passed to LWP::UserAgent', }, # CrowdSec plugin crowdsec => { type => 'bool', documentation => 'CrowdSec plugin activation', }, crowdsecAction => { type => 'select', select => [ { k => 'reject', v => 'Reject' }, { k => 'warn', v => 'Warn' }, ], default => 'reject', documentation => 'CrowdSec action', }, crowdsecUrl => { type => 'url', documentation => 'Base URL of CrowdSec local API', }, crowdsecKey => { type => 'text', documentation => 'CrowdSec API key', }, # History failedLoginNumber => { default => 5, type => 'int', documentation => 'Number of failures stored in login history', }, loginHistoryEnabled => { default => 0, type => 'bool', documentation => 'Enable login history', }, portalDisplayLoginHistory => { type => 'boolOrExpr', default => 1, documentation => 'Display login history tab in portal', }, successLoginNumber => { default => 5, type => 'int', documentation => 'Number of success stored in login history', }, # Other displays portalDisplayAppslist => { type => 'boolOrExpr', default => 1, documentation => 'Display applications tab in portal', }, portalDisplayChangePassword => { type => 'boolOrExpr', default => '$_auth =~ /^(LDAP|DBI|Demo)$/', documentation => 'Display password tab in portal', }, portalDisplayLogout => { default => 1, type => 'boolOrExpr', documentation => 'Display logout tab in portal', }, portalDisplayCertificateResetByMail => { type => 'bool', default => 0, documentation => 'Display certificate reset by mail button in portal', }, portalDisplayRegister => { default => 1, type => 'bool', documentation => 'Display register button in portal', }, portalDisplayResetPassword => { default => 0, type => 'bool', documentation => 'Display reset password button in portal', }, passwordResetAllowedRetries => { default => 3, type => 'int', documentation => 'Maximum number of retries to reset password', }, portalDisplayOidcConsents => { type => 'boolOrExpr', default => '$_oidcConsents && $_oidcConsents =~ /\w+/', documentation => 'Display OIDC consent tab in portal', }, portalDisplayGeneratePassword => { default => 1, type => 'bool', documentation => 'Display password generate box in reset password form', }, portalDisplayRefreshMyRights => { default => 1, type => 'bool', documentation => 'Display link to refresh the user session', }, portalEnablePasswordDisplay => { default => 0, type => 'bool', documentation => 'Allow to display password in login form', }, # Cookies cookieExpiration => { type => 'int', documentation => 'Cookie expiration', flags => 'hp', }, cookieName => { type => 'text', test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/, msgFail => '__badCookieName__', default => 'lemonldap', documentation => 'Name of the main cookie', flags => 'hp', }, domain => { type => 'text', test => qr/^(?:$Regexp::Common::URI::RFC2396::hostname)?$/, msgFail => '__badDomainName__', default => 'example.com', documentation => 'DNS domain', flags => 'hp', }, pdataDomain => { type => 'text', test => qr/^(?:$Regexp::Common::URI::RFC2396::hostname)?$/, msgFail => '__badDomainName__', default => '', documentation => 'pdata cookie DNS domain', flags => 'hp', }, httpOnly => { default => 1, type => 'bool', documentation => 'Enable httpOnly flag in cookie', flags => 'hp', }, securedCookie => { type => 'select', select => [ { k => '0', v => 'unsecuredCookie' }, { k => '1', v => 'securedCookie' }, { k => '2', v => 'doubleCookie' }, { k => '3', v => 'doubleCookieForSingleSession' }, ], default => 0, documentation => 'Cookie securisation method', flags => 'hp', }, sameSite => { type => 'select', select => [ { k => '', v => '' }, { k => 'Strict', v => 'Strict' }, { k => 'Lax', v => 'Lax' }, { k => 'None', v => 'None' }, ], default => '', documentation => 'Cookie SameSite value', flags => 'hp', }, # Viewer viewerHiddenKeys => { type => 'text', default => 'samlIDPMetaDataNodes, samlSPMetaDataNodes', documentation => 'Hidden Conf keys', flags => 'm', }, viewerAllowBrowser => { type => 'bool', default => 0, documentation => 'Allow configuration browser', }, viewerAllowDiff => { type => 'bool', default => 0, documentation => 'Allow configuration diff', }, # Notification oldNotifFormat => { type => 'bool', default => 0, documentation => 'Use old XML format for notifications', }, notificationWildcard => { type => 'text', default => 'allusers', documentation => 'Notification string to match all users', }, notificationXSLTfile => { type => 'text', documentation => 'Custom XSLT document for notifications', }, notification => { default => 0, type => 'bool', documentation => 'Notification activation', }, notificationsExplorer => { default => 0, type => 'bool', documentation => 'Notifications explorer activation', }, notificationsMaxRetrieve => { default => 3, type => 'int', documentation => 'Max number of displayed notifications', }, notificationServer => { default => 0, type => 'bool', documentation => 'Notification server activation', }, notificationDefaultCond => { type => 'text', default => '', documentation => 'Notification default condition', }, notificationServerGET => { default => 0, type => 'bool', documentation => 'Notification server activation', }, notificationServerPOST => { default => 1, type => 'bool', documentation => 'Notification server activation', }, notificationServerDELETE => { default => 0, type => 'bool', documentation => 'Notification server activation', }, notificationServerSentAttributes => { type => 'text', default => 'uid reference date title subtitle text check', documentation => 'Prameters to send with notification server GET method', flags => 'p', }, notificationStorage => { type => 'PerlModule', default => 'File', documentation => 'Notification backend', }, notificationStorageOptions => { type => 'keyTextContainer', default => { dirName => '/var/lib/lemonldap-ng/notifications', }, documentation => 'Notification backend options', }, # Captcha captcha_login_enabled => { default => 0, type => 'bool', documentation => 'Captcha on login page', }, captcha_mail_enabled => { default => 1, type => 'bool', documentation => 'Captcha on password reset page', }, captcha_register_enabled => { default => 1, type => 'bool', documentation => 'Captcha on account creation page', }, captcha_size => { type => 'int', default => 6, documentation => 'Captcha size', }, captcha => { type => 'PerlModule', documentation => 'Captcha backend module', flags => 'hp', }, captchaOptions => { type => 'keyTextContainer', documentation => 'Captcha module options', flags => 'hp', }, # Variables exportedVars => { type => 'keyTextContainer', help => 'exportedvars.html', keyTest => qr/^!?[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badVariableName__', test => qr/^[_a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => { 'UA' => 'HTTP_USER_AGENT' }, documentation => 'Main exported variables', }, groups => { type => 'keyTextContainer', help => 'exportedvars.html#extend-variables-using-macros-and-groups', test => sub { return perlExpr(@_) }, default => {}, documentation => 'Groups', }, macros => { type => 'keyTextContainer', help => 'exportedvars.html#extend-variables-using-macros-and-groups', keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badMacroName__', test => sub { return perlExpr(@_) }, default => {}, documentation => 'Macros', }, # Storage globalStorage => { type => 'PerlModule', default => 'Apache::Session::File', documentation => 'Session backend module', flags => 'hp', }, globalStorageOptions => { type => 'keyTextContainer', default => { 'Directory' => '/var/lib/lemonldap-ng/sessions/', 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/', 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', }, documentation => 'Session backend module options', flags => 'hp', }, localSessionStorage => { type => 'PerlModule', default => 'Cache::FileCache', documentation => 'Local sessions cache module', }, localSessionStorageOptions => { type => 'keyTextContainer', default => { 'namespace' => 'lemonldap-ng-sessions', 'default_expires_in' => 600, 'directory_umask' => '007', 'cache_root' => '/var/cache/lemonldap-ng', 'cache_depth' => 3, }, documentation => 'Sessions cache module options', }, # Persistent storage persistentStorage => { type => 'PerlModule', documentation => 'Storage module for persistent sessions' }, persistentStorageOptions => { type => 'keyTextContainer', documentation => 'Options for persistent sessions storage module' }, sessionDataToRemember => { type => 'keyTextContainer', keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__invalidSessionData__', documentation => 'Data to remember in login history', }, disablePersistentStorage => { default => 0, type => 'bool', documentation => 'Enabled persistent storage', }, # SAML issuer issuerDBSAMLActivation => { default => 0, type => 'bool', documentation => 'SAML IDP activation', }, issuerDBSAMLPath => { type => 'pcre', default => '^/saml/', documentation => 'SAML IDP request path', }, issuerDBSAMLRule => { type => 'boolOrExpr', default => 1, documentation => 'SAML IDP rule', }, # OpenID-Connect issuer issuerDBOpenIDConnectActivation => { type => 'bool', default => 0, documentation => 'OpenID Connect server activation', }, issuerDBOpenIDConnectPath => { type => 'text', default => '^/oauth2/', documentation => 'OpenID Connect server request path', }, issuerDBOpenIDConnectRule => { type => 'boolOrExpr', default => 1, documentation => 'OpenID Connect server rule', }, # GET issuer issuerDBGetActivation => { type => 'bool', default => 0, documentation => 'Get issuer activation', }, issuerDBGetPath => { type => 'text', default => '^/get/', documentation => 'Get issuer request path', }, issuerDBGetRule => { type => 'boolOrExpr', default => 1, documentation => 'Get issuer rule', }, issuerDBGetParameters => { type => 'doubleHash', default => {}, keyTest => qr/^$Regexp::Common::URI::RFC2396::hostname$/, keyMsgFail => '__badHostname__', test => { keyTest => qr/^(?=[^\-])[\w\-]+(?<=[^-])$/, keyMsgFail => '__badKeyName__', test => sub { my ( $val, $conf ) = @_; return 1 if ( defined $conf->{macros}->{$val} or $val eq '_timezone' ); foreach ( keys %$conf ) { return 1 if ( $_ =~ /exportedvars$/i and defined $conf->{$_}->{$val} ); } return ( 1, "__unknownAttrOrMacro__: $val" ); }, }, documentation => 'List of virtualHosts with their get parameters', }, # Password mailOnPasswordChange => { default => 0, type => 'bool', documentation => 'Send a mail when password is changed', }, portalRequireOldPassword => { default => 1, type => 'boolOrExpr', documentation => 'Rule to require old password to change the password', }, hideOldPassword => { default => 0, type => 'bool', documentation => 'Hide old password in portal', }, passwordPolicyActivation => { type => 'boolOrExpr', default => 1, documentation => 'Enable password policy', }, passwordPolicyMinSize => { default => 0, type => 'int', documentation => 'Password policy: minimal size', }, passwordPolicyMinLower => { default => 0, type => 'int', documentation => 'Password policy: minimal lower characters', }, passwordPolicyMinUpper => { default => 0, type => 'int', documentation => 'Password policy: minimal upper characters', }, passwordPolicyMinDigit => { default => 0, type => 'int', documentation => 'Password policy: minimal digit characters', }, passwordPolicyMinSpeChar => { default => 0, type => 'int', documentation => 'Password policy: minimal special characters', }, passwordPolicySpecialChar => { default => '__ALL__', type => 'text', test => qr/^(?:__ALL__|[\S\W]*)$/, documentation => 'Password policy: allowed special characters', }, portalDisplayPasswordPolicy => { default => 0, type => 'bool', documentation => 'Display policy in password form', }, # SMTP server SMTPServer => { type => 'text', default => '', test => qr/^(?:$Regexp::Common::URI::RFC2396::host(?::\d+)?)?$/, documentation => 'SMTP Server', }, SMTPPort => { type => 'int', documentation => 'Fix SMTP port', }, SMTPTLS => { type => 'select', default => '', select => [ { k => '', v => 'none' }, { k => 'starttls', v => 'SMTP + STARTTLS' }, { k => 'ssl', v => 'SMTPS' }, ], documentation => 'TLS protocol to use with SMTP', }, SMTPTLSOpts => { type => 'keyTextContainer', documentation => 'TLS/SSL options for SMTP', }, SMTPAuthUser => { type => 'text', documentation => 'Login to use to send mails', }, SMTPAuthPass => { type => 'password', documentation => 'Password to use to send mails', }, # Mails mailCharset => { type => 'text', default => 'utf-8', documentation => 'Mail charset', }, mailFrom => { type => 'text', default => 'noreply@example.com', documentation => 'Sender email', }, mailSessionKey => { type => 'text', default => 'mail', documentation => 'Session parameter where mail is stored', }, mailReplyTo => { type => 'text', documentation => 'Reply-To address' }, mailTimeout => { type => 'int', default => 0, documentation => 'Mail password reset session timeout', }, # Password reset mailBody => { type => 'longtext', documentation => 'Custom password reset mail body', }, mailConfirmBody => { type => 'longtext', documentation => 'Custom confirm password reset mail body', }, mailConfirmSubject => { type => 'text', documentation => 'Mail subject for reset confirmation', }, mailSubject => { type => 'text', documentation => 'Mail subject for new password email', }, mailUrl => { type => 'url', default => 'http://auth.example.com/resetpwd', documentation => 'URL of password reset page', }, # Certificate reset by mail certificateResetByMailCeaAttribute => { type => 'text', default => 'description' }, certificateResetByMailCertificateAttribute => { type => 'text', default => 'userCertificate;binary', }, certificateResetByMailStep1Subject => { type => 'text', documentation => 'Mail subject for certificate reset email', }, certificateResetByMailStep1Body => { type => 'longtext', documentation => 'Custom Certificate reset mail body', }, certificateResetByMailStep2Subject => { type => 'text', documentation => 'Mail subject for reset confirmation', }, certificateResetByMailStep2Body => { type => 'longtext', documentation => 'Custom confirm Certificate reset mail body', }, certificateResetByMailURL => { type => 'url', default => 'http://auth.example.com/certificateReset', documentation => 'URL of certificate reset page', }, certificateResetByMailValidityDelay => { type => 'int', default => 0 }, # Registration registerConfirmSubject => { type => 'text', documentation => 'Mail subject for register confirmation', }, registerConfirmBody => { type => 'longtext', documentation => 'Mail body for register confirmation', }, registerDoneSubject => { type => 'text', documentation => 'Mail subject when register is done', }, registerDoneBody => { type => 'longtext', documentation => 'Mail body when register is done', }, registerTimeout => { default => 0, type => 'int', documentation => 'Register session timeout', }, registerUrl => { type => 'text', default => 'http://auth.example.com/register', documentation => 'URL of register page', }, registerDB => { type => 'select', select => [ { k => 'AD', v => 'Active Directory' }, { k => 'Demo', v => 'Demonstration' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Null', v => 'None' }, { k => 'Custom', v => 'customModule' }, ], default => 'Null', documentation => 'Register module', }, # Upgrade session upgradeSession => { type => 'bool', default => 1, documentation => 'Upgrade session activation', }, forceGlobalStorageUpgradeOTT => { type => 'bool', documentation => 'Force Upgrade tokens be stored into Global Storage', }, # 2F max2FDevices => { default => 10, type => 'int', documentation => 'Maximum registered 2F devices', }, max2FDevicesNameLength => { default => 20, type => 'int', documentation => 'Maximum 2F devices name length', }, # U2F u2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'U2F activation', }, u2fSelfRegistration => { type => 'boolOrExpr', default => 0, documentation => 'U2F self registration activation', }, u2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by password+U2F' }, u2fLabel => { type => 'text', documentation => 'Portal label for U2F' }, u2fLogo => { type => 'text', documentation => 'Custom logo for U2F', }, u2fUserCanRemoveKey => { type => 'bool', default => 1, documentation => 'Authorize users to remove existing U2F key', }, u2fTTL => { type => 'int', documentation => 'U2F device time to live', }, # TOTP second factor totp2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'TOTP activation', }, totp2fSelfRegistration => { type => 'boolOrExpr', default => 0, documentation => 'TOTP self registration activation', }, totp2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by password+TOTP' }, totp2fLabel => { type => 'text', documentation => 'Portal label for TOTP 2F' }, totp2fLogo => { type => 'text', documentation => 'Custom logo for TOTP 2F', }, totp2fIssuer => { type => 'text', documentation => 'TOTP Issuer', }, totp2fInterval => { type => 'int', default => 30, documentation => 'TOTP interval', }, totp2fRange => { type => 'int', default => 1, documentation => 'TOTP range (number of interval to test)', }, totp2fDigits => { type => 'int', default => 6, documentation => 'Number of digits for TOTP code', }, totp2fUserCanRemoveKey => { type => 'bool', default => 1, documentation => 'Authorize users to remove existing TOTP secret', }, totp2fTTL => { type => 'int', documentation => 'TOTP device time to live ', }, totp2fEncryptSecret => { type => 'bool', default => 0, documentation => 'Encrypt TOTP secrets in database', }, # UTOTP 2F utotp2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'UTOTP activation (mixed U2F/TOTP module)', }, utotp2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by password+(U2F or TOTP)' }, utotp2fLabel => { type => 'text', documentation => 'Portal label for U2F+TOTP' }, utotp2fLogo => { type => 'text', documentation => 'Custom logo for U2F+TOTP', }, # Mail second factor mail2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'Mail second factor activation', }, mail2fSubject => { type => 'text', documentation => 'Mail subject for second factor authentication', }, mail2fBody => { type => 'longtext', documentation => 'Mail body for second factor authentication', }, mail2fCodeRegex => { type => 'pcre', default => '\d{6}', documentation => 'Regular expression to create a mail OTP code', }, mail2fTimeout => { type => 'int', documentation => 'Second factor code timeout', }, mail2fResendInterval => { type => 'text', documentation => 'Delay before user is allowed to resend code', }, mail2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authenticated by Mail second factor' }, mail2fLabel => { type => 'text', documentation => 'Portal label for Mail second factor' }, mail2fLogo => { type => 'text', documentation => 'Custom logo for Mail 2F', }, mail2fSessionKey => { type => 'text', documentation => 'Session parameter where mail is stored', }, # External second factor ext2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'External second factor activation', }, ext2fCodeActivation => { type => 'pcre', default => '\d{6}', documentation => 'OTP generated by Portal', }, ext2FSendCommand => { type => 'text', documentation => 'Send command of External second factor', }, ext2FValidateCommand => { type => 'text', documentation => 'Validation command of External second factor', }, ext2fResendInterval => { type => 'text', documentation => 'Delay before user is allowed to resend code', }, ext2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by External second factor' }, ext2fLabel => { type => 'text', documentation => 'Portal label for External second factor' }, ext2fLogo => { type => 'text', documentation => 'Custom logo for External 2F', }, # Radius second factor radius2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'Radius second factor activation', }, radius2fSecret => { type => 'text', }, radius2fServer => { type => 'text', }, radius2fUsernameSessionKey => { type => 'text', documentation => 'Session key used as Radius login' }, radius2fTimeout => { type => 'int', default => 20, documentation => 'Radius 2f verification timeout', }, radius2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authenticated by Radius second factor' }, radius2fLogo => { type => 'text', documentation => 'Custom logo for Radius 2F', }, radius2fLabel => { type => 'text', documentation => 'Portal label for Radius 2F' }, # REST External second factor rest2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'REST second factor activation', }, rest2fCodeActivation => { type => 'pcre', documentation => 'OTP generated by Portal', }, rest2fInitUrl => { type => 'url', documentation => 'REST 2F init URL', }, rest2fInitArgs => { type => 'keyTextContainer', keyTest => qr/^\w+$/, keyMsgFail => '__badKeyName__', test => qr/^\w+$/, msgFail => '__badValue__', documentation => 'Args for REST 2F init', }, rest2fVerifyUrl => { type => 'url', keyTest => qr/^\w+$/, keyMsgFail => '__badKeyName__', test => qr/^\w+$/, msgFail => '__badValue__', documentation => 'REST 2F init URL', }, rest2fVerifyArgs => { type => 'keyTextContainer', documentation => 'Args for REST 2F init', }, rest2fResendInterval => { type => 'text', documentation => 'Delay before user is allowed to resend code', }, rest2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by REST second factor' }, rest2fLabel => { type => 'text', documentation => 'Portal label for REST second factor' }, rest2fLogo => { type => 'text', documentation => 'Custom logo for REST 2F', }, # Yubikey 2FA yubikey2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'Yubikey second factor activation', }, yubikey2fSelfRegistration => { type => 'boolOrExpr', default => 0, documentation => 'Yubikey self registration activation', }, yubikey2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by Yubikey second factor' }, yubikey2fLabel => { type => 'text', documentation => 'Portal label for Yubikey second factor' }, yubikey2fLogo => { type => 'text', documentation => 'Custom logo for Yubikey 2F', }, yubikey2fClientID => { type => 'text', documentation => 'Yubico client ID', }, yubikey2fSecretKey => { type => 'text', documentation => 'Yubico secret key', }, yubikey2fNonce => { type => 'text', documentation => 'Yubico nonce', }, yubikey2fUrl => { type => 'text', documentation => 'Yubico server', }, yubikey2fPublicIDSize => { type => 'int', default => 12, documentation => 'Yubikey public ID size', }, yubikey2fUserCanRemoveKey => { type => 'bool', default => 1, documentation => 'Authorize users to remove existing Yubikey', }, yubikey2fFromSessionAttribute => { type => 'text', documentation => 'Provision yubikey from the given session variable', }, yubikey2fTTL => { type => 'int', documentation => 'Yubikey device time to live', }, # WebAuthn 2FA webauthn2fActivation => { type => 'boolOrExpr', default => 0, documentation => 'WebAuthn second factor activation', }, webauthn2fSelfRegistration => { type => 'boolOrExpr', default => 0, documentation => 'WebAuthn self registration activation', }, webauthn2fAuthnLevel => { type => 'int', documentation => 'Authentication level for users authentified by WebAuthn second factor' }, webauthn2fLabel => { type => 'text', documentation => 'Portal label for WebAuthn second factor' }, webauthn2fLogo => { type => 'text', documentation => 'Custom logo for WebAuthn 2F', }, webauthn2fUserVerification => { type => 'select', select => [ { k => 'discouraged', v => 'Discouraged' }, { k => 'preferred', v => 'Preferred' }, { k => 'required', v => 'Required' }, ], default => 'preferred', documentation => 'Verify user during registration and login', }, webauthn2fUserCanRemoveKey => { type => 'bool', default => 1, documentation => 'Authorize users to remove existing WebAuthn', }, webauthnDisplayNameAttr => { type => 'text', documentation => 'Session attribute containing user display name', }, webauthnRpName => { type => 'text', documentation => 'WebAuthn Relying Party display name', }, # Single session notifyDeleted => { default => 1, type => 'bool', documentation => 'Show deleted sessions in portal', }, notifyOther => { default => 0, type => 'bool', documentation => 'Show other sessions in portal', }, singleSession => { default => 0, type => 'boolOrExpr', documentation => 'Allow only one session per user', }, singleIP => { default => 0, type => 'boolOrExpr', documentation => 'Allow only one session per IP', }, singleUserByIP => { default => 0, type => 'boolOrExpr', documentation => 'Allow only one user per IP', }, # REST server restSessionServer => { default => 0, type => 'bool', documentation => 'Enable REST session server', }, restAuthServer => { default => 0, type => 'bool', documentation => 'Enable REST authentication server', }, restPasswordServer => { default => 0, type => 'bool', documentation => 'Enable REST password reset server', }, restExportSecretKeys => { default => 0, type => 'bool', documentation => 'Allow to export secret keys in REST session server', }, restClockTolerance => { default => 15, type => 'int', documentation => 'How tolerant the REST session server will be to clock dift', }, restConfigServer => { default => 0, type => 'bool', documentation => 'Enable REST config server', }, restAuthnLevel => { type => 'int', default => 2, documentation => 'REST authentication level', }, # SOAP server soapSessionServer => { default => 0, type => 'bool', help => 'soapservices.html', documentation => 'Enable SOAP session server', }, soapConfigServer => { default => 0, type => 'bool', help => 'soapservices.html', documentation => 'Enable SOAP config server', }, exportedAttr => { type => 'text', documentation => 'List of attributes to export by SOAP or REST servers', }, wsdlServer => { default => 0, type => 'bool', documentation => 'Enable /portal.wsdl server', }, # SOAP Procy client soapProxyUrn => { default => 'urn:Lemonldap/NG/Common/PSGI/SOAPService', type => 'text', documentation => 'SOAP URN for Proxy', }, # AutoSignin autoSigninRules => { type => 'keyTextContainer', documentation => 'List of auto signin rules', }, # Adaptative Authentication Level plugin adaptativeAuthenticationLevelRules => { type => 'keyTextContainer', keyTest => sub { eval { qr/$_[0]/ }; return $@ ? 0 : 1; }, keyMsgFail => '__badRegexp__', help => "adaptativeauthenticationlevel.html", documentation => 'Adaptative authentication level rules', flags => 'p', }, ## Virtualhosts # Fake attribute: used by manager REST API to agglomerate all other # nodes virtualHosts => { type => 'virtualHostContainer', help => 'configvhost.html', template => 'virtualHost', }, locationRules => { type => 'ruleContainer', help => 'writingrulesand_headers.html#rules', test => { keyTest => sub { eval { qr/$_[0]/ }; return $@ ? 0 : 1; }, keyMsgFail => '__badRegexp__', test => sub { my ( $val, $conf ) = @_; my $s = $val; if ( $s =~ s/^logout(?:_(?:sso|app(?:_sso)?))?\s*// ) { return $s =~ m{^(?:https?://.*)?$} ? (1) : ( 0, '__badUrl__' ); } $s =~ s/\b(accept|deny|unprotect|skip)\b/1/g; return &perlExpr( $s, $conf ); }, msgFail => '__badExpression__', }, keyTest => qr/^\S+$/, keyMsgFail => '__badHostname__', default => { default => 'deny', }, documentation => 'Virtualhost rules', flags => 'h', }, exportedHeaders => { type => 'keyTextContainer', help => 'writingrulesand_headers.html#headers', keyTest => qr/^\S+$/, keyMsgFail => '__badHostname__', test => { keyTest => qr/^(?=[^\-])[\w\-]+(?<=[^-])$/, keyMsgFail => '__badHeaderName__', test => sub { return perlExpr(@_) }, }, documentation => 'Virtualhost headers', flags => 'h', }, post => { type => 'postContainer', help => 'formreplay.html', test => sub { 1 }, keyTest => qr/^\S+$/, keyMsgFail => '__badHostname__', documentation => 'Virtualhost urls/Data to post', }, vhostOptions => { type => 'subContainer', }, vhostPort => { type => 'int', default => -1, }, vhostHttps => { type => 'trool', default => -1, }, vhostMaintenance => { type => 'bool', default => 0, }, vhostServiceTokenTTL => { type => 'int', default => -1, }, vhostAccessToTrace => { type => 'text', default => '' }, vhostAliases => { type => 'text', default => '' }, vhostType => { type => 'select', select => [ { k => 'AuthBasic', v => 'AuthBasic' }, { k => 'CDA', v => 'CDA' }, { k => 'DevOps', v => 'DevOps' }, { k => 'DevOpsST', v => 'DevOpsST' }, { k => 'Main', v => 'Main' }, { k => 'OAuth2', v => 'OAuth2' }, { k => 'SecureToken', v => 'SecureToken' }, { k => 'ServiceToken', v => 'ServiceToken' }, { k => 'ZimbraPreAuth', v => 'ZimbraPreAuth' }, ], default => 'Main', documentation => 'Handler type', }, vhostAuthnLevel => { type => 'int' }, vhostDevOpsRulesUrl => { type => 'url' }, # SecureToken parameters secureTokenAllowOnError => { type => 'text', documentation => 'Secure Token allow requests in error', flags => 'h', }, secureTokenAttribute => { type => 'text', documentation => 'Secure Token attribute', flags => 'h', }, secureTokenExpiration => { type => 'text', documentation => 'Secure Token expiration', flags => 'h', }, secureTokenHeader => { type => 'text', documentation => 'Secure Token header', flags => 'h', }, secureTokenMemcachedServers => { type => 'text', documentation => 'Secure Token Memcached servers', flags => 'h', }, secureTokenUrls => { type => 'text', documentation => '', flags => 'h', }, # Zimbra handler parameters zimbraAccountKey => { type => 'text', flags => 'h', documentation => 'Zimbra account session key', }, zimbraBy => { type => 'text', flags => 'h', documentation => 'Zimbra account type', }, zimbraPreAuthKey => { type => 'text', flags => 'h', documentation => 'Zimbra preauthentication key', }, zimbraSsoUrl => { type => 'text', flags => 'h', documentation => 'Zimbra local SSO URL pattern', }, zimbraUrl => { type => 'text', flags => 'h', documentation => 'Zimbra preauthentication URL', }, # CAS IDP casAttr => { type => 'text', documentation => 'Pivot attribute for CAS', }, casAttributes => { type => 'keyTextContainer', documentation => 'CAS exported attributes', }, casAccessControlPolicy => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'error', v => 'Display error on portal' }, { k => 'faketicket', v => 'Send a fake service ticket' }, ], default => 'none', documentation => 'CAS access control policy', }, casStorage => { type => 'PerlModule', documentation => 'Apache::Session module to store CAS user data', }, casStorageOptions => { type => 'keyTextContainer', documentation => 'Apache::Session module parameters', }, casStrictMatching => { default => 0, type => 'bool', documentation => 'Disable host-based matching of CAS services', }, casTicketExpiration => { default => 0, type => 'int', documentation => 'Expiration time of Service and Proxy tickets', }, issuerDBCASActivation => { default => 0, type => 'bool', documentation => 'CAS server activation', }, issuerDBCASPath => { type => 'pcre', default => '^/cas/', documentation => 'CAS server request path', }, issuerDBCASRule => { type => 'boolOrExpr', default => 1, documentation => 'CAS server rule', }, # Partners casAppMetaDataOptions => { type => 'subContainer', documentation => 'Root of CAS app options', }, casAppMetaDataExportedVars => { type => 'keyTextContainer', default => { cn => 'cn', mail => 'mail', uid => 'uid', }, documentation => 'CAS exported variables', }, casAppMetaDataOptionsService => { type => 'text', documentation => 'CAS App service', }, casAppMetaDataOptionsUserAttribute => { type => 'text', documentation => 'CAS User attribute', }, casAppMetaDataOptionsAuthnLevel => { type => 'int', documentation => 'Authentication level requires to access to this CAS application', }, casAppMetaDataOptionsRule => { type => 'text', test => sub { return perlExpr(@_) }, documentation => 'CAS App rule', }, casAppMetaDataMacros => { type => 'keyTextContainer', help => 'exportedvars.html#extend-variables-using-macros-and-groups', test => { keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badMacroName__', test => sub { return perlExpr(@_) }, }, default => {}, documentation => 'Macros', }, # Fake attribute: used by manager REST API to agglomerate all nodes # related to a CAS SP partner casAppMetaDataNodes => { type => 'casAppMetaDataNodeContainer', template => 'casAppMetaDataNode', help => 'idpcas.html#configuring-cas-applications', }, # OpenID Issuer issuerDBOpenIDActivation => { default => 0, type => 'bool', documentation => 'OpenID server activation', }, issuerDBOpenIDPath => { type => 'pcre', default => '^/openidserver/', documentation => 'OpenID server request path', }, issuerDBOpenIDRule => { type => 'boolOrExpr', default => 1, documentation => 'OpenID server rule', }, openIdIssuerSecret => { type => 'text', }, openIdAttr => { type => 'text', }, openIdSreg_fullname => { type => 'lmAttrOrMacro', default => 'cn', documentation => 'OpenID SREG fullname session parameter', }, openIdSreg_nickname => { type => 'lmAttrOrMacro', default => 'uid', documentation => 'OpenID SREG nickname session parameter', }, openIdSreg_language => { type => 'lmAttrOrMacro', }, openIdSreg_postcode => { type => 'lmAttrOrMacro', }, openIdSreg_timezone => { type => 'lmAttrOrMacro', default => '_timezone', documentation => 'OpenID SREG timezone session parameter', }, openIdSreg_country => { type => 'lmAttrOrMacro', }, openIdSreg_gender => { type => 'lmAttrOrMacro', }, openIdSreg_email => { type => 'lmAttrOrMacro', default => 'mail', documentation => 'OpenID SREG email session parameter', }, openIdSreg_dob => { type => 'lmAttrOrMacro', }, openIdSPList => { type => 'blackWhiteList', default => '0;' }, ######### ## SAML # ######### samlEntityID => { type => 'text', default => '#PORTAL#/saml/metadata', documentation => 'SAML service entityID', }, samlOrganizationDisplayName => { type => 'text', default => 'Example', documentation => 'SAML service organization display name', }, samlOrganizationName => { type => 'text', default => 'Example', documentation => 'SAML service organization name', }, samlOrganizationURL => { type => 'text', default => 'http://www.example.com', documentation => 'SAML service organization URL', }, samlNameIDFormatMapEmail => { type => 'text', default => 'mail', documentation => 'SAML session parameter for NameID email', }, samlNameIDFormatMapX509 => { type => 'text', default => 'mail', documentation => 'SAML session parameter for NameID x509', }, samlNameIDFormatMapWindows => { type => 'text', default => 'uid', documentation => 'SAML session parameter for NameID windows', }, samlNameIDFormatMapKerberos => { type => 'text', default => 'uid', documentation => 'SAML session parameter for NameID kerberos', }, samlAttributeAuthorityDescriptorAttributeServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/AA/SOAP;', documentation => 'SAML Attribute Authority SOAP', }, samlServicePrivateKeySig => { type => 'RSAPrivateKey', default => '', documentation => 'SAML signature private key', }, samlServicePrivateKeySigPwd => { type => 'password', default => '', documentation => 'SAML signature private key password', }, samlServicePublicKeySig => { type => 'RSAPublicKeyOrCertificate', default => '', documentation => 'SAML signature public key', }, samlServicePrivateKeyEnc => { type => 'RSAPrivateKey', default => '', documentation => 'SAML encryption private key', }, samlServicePrivateKeyEncPwd => { type => 'password', }, samlServicePublicKeyEnc => { type => 'RSAPublicKeyOrCertificate', default => '', documentation => 'SAML encryption public key', }, samlServiceSignatureMethod => { type => 'select', select => [ { k => 'RSA_SHA1', v => 'RSA SHA1' }, { k => 'RSA_SHA256', v => 'RSA SHA256' }, { k => 'RSA_SHA384', v => 'RSA SHA384' }, { k => 'RSA_SHA512', v => 'RSA SHA512' }, ], default => 'RSA_SHA256', }, samlServiceUseCertificateInResponse => { type => 'bool', default => 0, documentation => 'Use certificate instead of public key in SAML responses', }, samlMetadataForceUTF8 => { default => 1, type => 'bool', documentation => 'SAML force metadata UTF8 conversion', }, samlStorage => { type => 'PerlModule', documentation => 'Apache::Session module to store SAML user data', }, samlStorageOptions => { type => 'keyTextContainer', documentation => 'Apache::Session module parameters', }, samlAuthnContextMapPassword => { type => 'int', default => 2, documentation => 'SAML authn context password level', }, samlAuthnContextMapPasswordProtectedTransport => { type => 'int', default => 3, documentation => 'SAML authn context password protected transport level', }, samlAuthnContextMapTLSClient => { type => 'int', default => 5, documentation => 'SAML authn context TLS client level', }, samlAuthnContextMapKerberos => { type => 'int', default => 4, documentation => 'SAML authn context kerberos level', }, samlCommonDomainCookieActivation => { default => 0, type => 'bool', documentation => 'SAML CDC activation', }, samlCommonDomainCookieDomain => { type => 'text', test => qr/^$Regexp::Common::URI::RFC2396::hostname$/, msgFail => '__badDomainName__', }, samlCommonDomainCookieReader => { type => 'text', test => $url, msgFail => '__badUrl__', }, samlCommonDomainCookieWriter => { type => 'text', test => $url, msgFail => '__badUrl__', }, samlDiscoveryProtocolActivation => { default => 0, type => 'bool', documentation => 'SAML Discovery Protocol activation', }, samlDiscoveryProtocolURL => { type => 'text', test => $url, msgFail => '__badUrl__', documentation => 'SAML Discovery Protocol EndPoint URL', }, samlDiscoveryProtocolPolicy => { type => 'text', documentation => 'SAML Discovery Protocol Policy', }, samlDiscoveryProtocolIsPassive => { default => 0, type => 'bool', documentation => 'SAML Discovery Protocol Is Passive', }, samlRelayStateTimeout => { type => 'int', default => 600, documentation => 'SAML timeout of relay state', }, samlOverrideIDPEntityID => { type => 'text', documentation => 'Override SAML EntityID when acting as an IDP', default => '', }, samlUseQueryStringSpecific => { default => 0, type => 'bool', documentation => 'SAML use specific method for query_string', }, samlIDPSSODescriptorWantAuthnRequestsSigned => { type => 'bool', default => 1, documentation => 'SAML IDP want authn request signed', }, samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' . '#PORTAL#/saml/singleSignOn;', documentation => 'SAML IDP SSO HTTP Redirect', }, samlIDPSSODescriptorSingleSignOnServiceHTTPPost => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/singleSignOn;', documentation => 'SAML IDP SSO HTTP POST', }, samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;' . '#PORTAL#/saml/singleSignOnArtifact;', documentation => 'SAML IDP SSO HTTP Artifact', }, samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' . '#PORTAL#/saml/singleLogout;' . '#PORTAL#/saml/singleLogoutReturn', documentation => 'SAML IDP SLO HTTP Redirect', }, samlIDPSSODescriptorSingleLogoutServiceHTTPPost => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/singleLogout;' . '#PORTAL#/saml/singleLogoutReturn', documentation => 'SAML IDP SLO HTTP POST', }, samlIDPSSODescriptorSingleLogoutServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/singleLogoutSOAP;', documentation => 'SAML IDP SLO SOAP', }, samlIDPSSODescriptorArtifactResolutionServiceArtifact => { type => 'samlAssertion', default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/artifact', documentation => 'SAML IDP artifact resolution service', }, # Fake attribute: used by manager REST API to agglomerate all nodes # related to a SAML IDP partner samlIDPMetaDataNodes => { type => 'samlIDPMetaDataNodeContainer', template => 'samlIDPMetaDataNode', help => 'authsaml.html', }, # Fake attribute: used by manager REST API to agglomerate all nodes # related to a SAML SP partner samlSPMetaDataNodes => { type => 'samlSPMetaDataNodeContainer', template => 'samlSPMetaDataNode', help => 'idpsaml.html', }, # TODO: split that # IDP Keys samlIDPMetaDataExportedAttributes => { type => 'samlAttributeContainer', help => 'authsaml.html#exported-attributes', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', test => qr/\w/, msgFail => '__badValue__', default => {}, }, samlIDPMetaDataXML => { type => 'file', test => sub { my $v = shift; return 1 unless ( $v and %$v ); my @msg; my $res = 1; my %entityIds; foreach my $idpId ( keys %$v ) { unless ( $v->{$idpId}->{samlIDPMetaDataXML} =~ /entityID="(.+?)"/si ) { push @msg, "$idpId SAML metadata has no EntityID"; $res = 0; next; } my $eid = $1; if ( defined $entityIds{$eid} ) { push @msg, "$idpId and $entityIds{$eid} have the same SAML EntityID"; $res = 0; next; } $entityIds{$eid} = $idpId; } return ( $res, join( ', ', @msg ) ); }, }, samlIDPMetaDataOptions => { type => 'keyTextContainer', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', }, samlIDPMetaDataOptionsNameIDFormat => { type => 'select', select => [ { k => '', v => '' }, { k => 'unspecified', v => 'Unspecified' }, { k => 'email', v => 'Email' }, { k => 'x509', v => 'X509 certificate' }, { k => 'windows', v => 'Windows' }, { k => 'kerberos', v => 'Kerberos' }, { k => 'entity', v => 'Entity' }, { k => 'persistent', v => 'Persistent' }, { k => 'transient', v => 'Transient' }, { k => 'encrypted', v => 'Encrypted' }, ], default => '', }, samlIDPMetaDataOptionsForceAuthn => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsIsPassive => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsAllowProxiedAuthn => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsAllowLoginFromIDP => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsRequestedAuthnContext => { type => 'select', select => [ { k => '', v => '' }, { k => 'kerberos', v => 'Kerberos' }, { k => 'password-protected-transport', v => 'Password protected transport' }, { k => 'password', v => 'Password' }, { k => 'tls-client', v => 'TLS client certificate' }, ], default => '', }, samlIDPMetaDataOptionsAdaptSessionUtime => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsForceUTF8 => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsSignSSOMessage => { type => 'trool', default => -1, }, samlIDPMetaDataOptionsCheckSSOMessageSignature => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsSignSLOMessage => { type => 'trool', default => -1, }, samlIDPMetaDataOptionsSignatureMethod => { type => 'select', select => [ { k => '', v => 'default' }, { k => 'RSA_SHA1', v => 'RSA SHA1' }, { k => 'RSA_SHA256', v => 'RSA SHA256' }, { k => 'RSA_SHA384', v => 'RSA SHA384' }, { k => 'RSA_SHA512', v => 'RSA SHA512' }, ], default => '', }, samlIDPMetaDataOptionsCheckSLOMessageSignature => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsSSOBinding => { type => 'select', select => [ { k => '', v => '' }, { k => 'http-post', v => 'POST' }, { k => 'http-redirect', v => 'Redirect' }, { k => 'artifact-get', v => 'Artifact GET' }, ], default => '', }, samlIDPMetaDataOptionsSLOBinding => { type => 'select', select => [ { k => '', v => '' }, { k => 'http-post', v => 'POST' }, { k => 'http-redirect', v => 'Redirect' }, { k => 'http-soap', v => 'SOAP' }, ], default => '', }, samlIDPMetaDataOptionsEncryptionMode => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'nameid', v => 'Name ID' }, { k => 'assertion', v => 'Assertion' }, ], default => 'none', }, samlIDPMetaDataOptionsCheckTime => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsCheckAudience => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsResolutionRule => { type => 'longtext', default => '', }, samlIDPMetaDataOptionsStoreSAMLToken => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsRelayStateURL => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsUserAttribute => { type => 'text', }, samlIDPMetaDataOptionsDisplayName => { type => 'text', }, samlIDPMetaDataOptionsIcon => { type => 'text', }, samlIDPMetaDataOptionsSortNumber => { type => 'int', }, # SP keys samlSPMetaDataExportedAttributes => { type => 'samlAttributeContainer', help => 'idpsaml.html#exported-attributes', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', test => qr/\w/, msgFail => '__badValue__', default => {}, }, samlSPMetaDataXML => { type => 'file', }, samlSPMetaDataOptions => { type => 'keyTextContainer', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', }, samlSPSSODescriptorAuthnRequestsSigned => { default => 1, type => 'bool', documentation => 'SAML SP AuthnRequestsSigned', }, samlSPSSODescriptorWantAssertionsSigned => { default => 1, type => 'bool', documentation => 'SAML SP WantAssertionsSigned', }, samlSPSSODescriptorSingleLogoutServiceHTTPRedirect => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' . '#PORTAL#/saml/proxySingleLogout;' . '#PORTAL#/saml/proxySingleLogoutReturn', documentation => 'SAML SP SLO HTTP Redirect', }, samlSPSSODescriptorSingleLogoutServiceHTTPPost => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/proxySingleLogout;' . '#PORTAL#/saml/proxySingleLogoutReturn', documentation => 'SAML SP SLO HTTP POST', }, samlSPSSODescriptorSingleLogoutServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/proxySingleLogoutSOAP;', documentation => 'SAML SP SLO SOAP', }, samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact => { type => 'samlAssertion', default => '0;1;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;' . '#PORTAL#/saml/proxySingleSignOnArtifact', documentation => 'SAML SP ACS HTTP artifact', }, samlSPSSODescriptorAssertionConsumerServiceHTTPPost => { type => 'samlAssertion', default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/proxySingleSignOnPost', documentation => 'SAML SP ACS HTTP POST', }, samlSPSSODescriptorArtifactResolutionServiceArtifact => { type => 'samlAssertion', default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/artifact', documentation => 'SAML SP artifact resolution service ', }, samlSPMetaDataOptionsNameIDFormat => { type => 'select', select => [ { k => '', v => '' }, { k => 'unspecified', v => 'Unspecified' }, { k => 'email', v => 'Email' }, { k => 'x509', v => 'X509 certificate' }, { k => 'windows', v => 'Windows' }, { k => 'kerberos', v => 'Kerberos' }, { k => 'entity', v => 'Entity' }, { k => 'persistent', v => 'Persistent' }, { k => 'transient', v => 'Transient' }, { k => 'encrypted', v => 'Encrypted' }, ], default => '', }, samlSPMetaDataOptionsNameIDSessionKey => { type => 'text', }, samlSPMetaDataOptionsOneTimeUse => { type => 'bool', default => 0, }, samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => { type => 'int', default => 72000, }, samlSPMetaDataOptionsNotOnOrAfterTimeout => { type => 'int', default => 72000, }, samlSPMetaDataOptionsSignSSOMessage => { type => 'trool', default => -1, }, samlSPMetaDataOptionsSignatureMethod => { type => 'select', select => [ { k => '', v => 'default' }, { k => 'RSA_SHA1', v => 'RSA SHA1' }, { k => 'RSA_SHA256', v => 'RSA SHA256' }, { k => 'RSA_SHA384', v => 'RSA SHA384' }, { k => 'RSA_SHA512', v => 'RSA SHA512' }, ], default => '', }, samlSPMetaDataOptionsCheckSSOMessageSignature => { type => 'bool', default => 1, }, samlSPMetaDataOptionsSignSLOMessage => { type => 'trool', default => -1, }, samlSPMetaDataOptionsCheckSLOMessageSignature => { type => 'bool', default => 1, }, samlSPMetaDataOptionsEncryptionMode => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'nameid', v => 'Name ID' }, { k => 'assertion', v => 'Assertion' }, ], default => 'none', }, samlSPMetaDataOptionsEnableIDPInitiatedURL => { type => 'bool', default => 0, }, samlSPMetaDataOptionsForceUTF8 => { type => 'bool', default => 1, }, samlSPMetaDataOptionsAuthnLevel => { type => 'int', documentation => 'Authentication level requires to access to this SP', }, samlSPMetaDataOptionsRule => { type => 'text', test => sub { return perlExpr(@_) }, documentation => 'Rule to grant access to this SP', }, samlSPMetaDataMacros => { type => 'keyTextContainer', help => 'exportedvars.html#extend-variables-using-macros-and-groups', test => { keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badMacroName__', test => sub { return perlExpr(@_) }, }, default => {}, documentation => 'Macros', }, # AUTH, USERDB and PASSWORD MODULES authentication => { type => 'select', select => [ { k => 'Apache', v => 'Apache' }, { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Facebook', v => 'Facebook' }, { k => 'GitHub', v => 'GitHub' }, { k => 'GPG', v => 'GPG' }, { k => 'Kerberos', v => 'Kerberos' }, { k => 'LDAP', v => 'LDAP' }, { k => 'LinkedIn', v => 'LinkedIn' }, { k => 'PAM', v => 'PAM' }, { k => 'Radius', v => 'Radius' }, { k => 'REST', v => 'REST' }, { k => 'SSL', v => 'SSL' }, { k => 'Twitter', v => 'Twitter' }, { k => 'WebID', v => 'WebID' }, { k => 'Demo', v => 'Demonstration' }, { k => 'Choice', v => 'authChoice' }, { k => 'Combination', v => 'combineMods' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Remote', v => 'Remote' }, { k => 'Slave', v => 'Slave' }, { k => 'Null', v => 'None' }, { k => 'Custom', v => 'customModule' }, ], default => 'Demo', documentation => 'Authentication module', }, userDB => { type => 'select', select => [ { k => 'Same', v => 'Same' }, { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'LDAP', v => 'LDAP' }, { k => 'REST', v => 'REST' }, { k => 'Null', v => 'None' }, { k => 'Custom', v => 'customModule' }, ], default => 'Same', documentation => 'User module', }, passwordDB => { type => 'select', select => [ { k => 'AD', v => 'Active Directory' }, { k => 'Choice', v => 'authChoice' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demonstration' }, { k => 'LDAP', v => 'LDAP' }, { k => 'REST', v => 'REST' }, { k => 'Null', v => 'None' }, { k => 'Combination', v => 'combineMods' }, { k => 'Custom', v => 'customModule' }, ], default => 'Demo', documentation => 'Password module', }, # Second Factor Engine sfEngine => { type => 'text', default => '::2F::Engines::Default', documentation => 'Second factor engine', }, sfRequired => { type => 'boolOrExpr', default => 0, help => 'secondfactor.html', documentation => 'Second factor required', }, sfOnlyUpgrade => { type => 'bool', help => 'secondfactor.html', documentation => 'Only trigger second factor on session upgrade', }, sfManagerRule => { type => 'boolOrExpr', default => 1, help => 'secondfactor.html', documentation => 'Rule to display second factor Manager link', }, sfRemovedMsgRule => { type => 'boolOrExpr', default => 0, help => 'secondfactor.html', documentation => 'Display a message if at leat one expired SF has been removed', }, sfRemovedUseNotif => { default => 0, type => 'bool', documentation => 'Use Notifications plugin to display message', }, sfRemovedNotifRef => { type => 'text', default => 'RemoveSF', help => 'secondfactor.html', documentation => 'Notification reference', }, sfRemovedNotifTitle => { type => 'text', default => 'Second factor notification', help => 'secondfactor.html', documentation => 'Notification title', }, sfRemovedNotifMsg => { type => 'text', default => '_removedSF_ expired second factor(s) has/have been removed (_nameSF_)!', help => 'secondfactor.html', documentation => 'Notification message', }, sfLoginTimeout => { type => 'int', documentation => 'Timeout for 2F login process', }, sfRegisterTimeout => { type => 'int', documentation => 'Timeout for 2F registration process', }, available2F => { type => 'text', default => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,WebAuthn,Yubikey,Radius', documentation => 'Available second factor modules', }, available2FSelfRegistration => { type => 'text', default => 'TOTP,U2F,WebAuthn,Yubikey', documentation => 'Available self-registration modules for second factor', }, # DEMO demoExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => { cn => 'cn', mail => 'mail', uid => 'uid', }, documentation => 'Demo exported variables', }, # AD ADPwdExpireWarning => { type => 'int', default => 0, documentation => 'AD password expire warning', }, ADPwdMaxAge => { type => 'int', default => 0, documentation => 'AD password max age', }, # LDAP managerDn => { type => 'text', test => qr/^.*$/, msgFail => '__badValue__', default => '', documentation => 'LDAP manager DN', }, managerPassword => { type => 'password', test => qr/^\S*$/, msgFail => '__badValue__', default => '', documentation => 'LDAP manager Password', }, ldapAuthnLevel => { type => 'int', default => 2, documentation => 'LDAP authentication level', }, ldapBase => { type => 'text', test => qr/^(?:\w+=.*|)$/, msgFail => '__badValue__', default => 'dc=example,dc=com', documentation => 'LDAP search base', }, ldapExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => { cn => 'cn', mail => 'mail', uid => 'uid', }, documentation => 'LDAP exported variables', }, ldapPort => { type => 'int', documentation => 'LDAP port', }, ldapServer => { type => 'text', test => sub { my $l = shift; my (@s) = split( /[\s,]+/, $l ); foreach my $s (@s) { $s =~ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?::\d{1,5})?/?.*)$}o or return ( 0, "__badLdapUri__: \"$s\"" ); } return 1; }, default => 'ldap://localhost', documentation => 'LDAP server (host or URI)', }, ldapPwdEnc => { type => 'text', test => qr/^[a-zA-Z0-9_][a-zA-Z0-9_\-]*[a-zA-Z0-9_]$/, msgFail => '__badEncoding__', default => 'utf-8', documentation => 'LDAP password encoding', }, ldapUsePasswordResetAttribute => { type => 'bool', default => 1, documentation => 'LDAP store reset flag in an attribute', }, ldapPasswordResetAttribute => { type => 'text', default => 'pwdReset', documentation => 'LDAP password reset attribute', }, ldapPasswordResetAttributeValue => { type => 'text', default => 'TRUE', documentation => 'LDAP password reset value', }, ldapPpolicyControl => { default => 0, type => 'bool', }, ldapSetPassword => { default => 0, type => 'bool', }, ldapChangePasswordAsUser => { default => 0, type => 'bool', }, ldapGetUserBeforePasswordChange => { default => 0, type => 'bool', }, ldapSearchDeref => { type => 'select', select => [ { k => 'never', v => 'never' }, { k => 'search', v => 'search' }, { k => 'find', v => 'find' }, { k => 'always', v => 'always' } ], default => 'find', documentation => '"deref" param of Net::LDAP::search()', }, mailLDAPFilter => { type => 'text', documentation => 'LDAP filter for mail search' }, LDAPFilter => { type => 'text', documentation => 'Default LDAP filter' }, AuthLDAPFilter => { type => 'text', documentation => 'LDAP filter for auth search' }, ldapGroupDecodeSearchedValue => { default => 0, type => 'bool', documentation => 'Decode value before searching it in LDAP groups', }, ldapGroupRecursive => { default => 0, type => 'bool', documentation => 'LDAP recursive search in groups', }, ldapGroupObjectClass => { type => 'text', default => 'groupOfNames', documentation => 'LDAP object class of groups', }, ldapGroupBase => { type => 'text', }, ldapGroupAttributeName => { type => 'text', default => 'member', documentation => 'LDAP attribute name for member in groups', }, ldapGroupAttributeNameUser => { type => 'text', default => 'dn', documentation => 'LDAP attribute name in user entry referenced as member in groups', }, ldapGroupAttributeNameSearch => { type => 'text', default => 'cn', documentation => 'LDAP attributes to search in groups', }, ldapGroupAttributeNameGroup => { type => 'text', default => 'dn', documentation => 'LDAP attribute name in group entry referenced as member in groups', }, ldapTimeout => { type => 'int', default => 10, documentation => 'LDAP connection timeout', }, ldapIOTimeout => { type => 'int', default => 10, documentation => 'LDAP operation timeout', }, ldapVersion => { type => 'int', default => 3, documentation => 'LDAP protocol version', }, ldapRaw => { type => 'text', }, ldapAllowResetExpiredPassword => { default => 0, type => 'bool', documentation => 'Allow a user to reset his expired password', }, ldapITDS => { default => 0, type => 'bool', documentation => 'Support for IBM Tivoli Directory Server', }, ldapVerify => { type => 'bool', documentation => 'Whether to validate LDAP certificates', type => "select", select => [ { k => 'none', v => 'None' }, { k => 'optional', v => 'Optional' }, { k => 'require', v => 'Require' }, ], default => 'require', }, ldapCAFile => { type => 'text', documentation => 'Location of the certificate file for LDAP connections', }, ldapCAPath => { type => 'text', documentation => 'Location of the CA directory for LDAP connections', }, # SSL SSLAuthnLevel => { type => 'int', default => 5, documentation => 'SSL authentication level', }, SSLVar => { type => 'text', default => 'SSL_CLIENT_S_DN_Email' }, SSLVarIf => { type => 'keyTextContainer', keyTest => sub { 1 }, default => {} }, sslByAjax => { type => 'bool', default => 0, documentation => 'Use Ajax request for SSL', }, sslHost => { type => 'url', documentation => 'URL for SSL Ajax request', }, # CAS casAuthnLevel => { type => 'int', default => 1, documentation => 'CAS authentication level', }, casSrvMetaDataExportedVars => { type => 'keyTextContainer', default => { cn => 'cn', mail => 'mail', uid => 'uid', }, documentation => 'CAS exported variables', }, casSrvMetaDataOptions => { type => 'subContainer', documentation => 'Root of CAS server options', }, casSrvMetaDataOptionsGateway => { type => 'bool', default => 0 }, casSrvMetaDataOptionsProxiedServices => { type => 'keyTextContainer', keyTest => qr/^\w/, keyMsgFail => '__badCasProxyId__', }, casSrvMetaDataOptionsRenew => { type => 'bool', default => 0 }, casSrvMetaDataOptionsUrl => { type => 'text', test => $url, msgFail => '__badUrl__', }, casSrvMetaDataOptionsDisplayName => { type => 'text', documentation => 'Name to display for CAS server', }, casSrvMetaDataOptionsIcon => { type => 'text', documentation => 'Path of CAS Server Icon', }, casSrvMetaDataOptionsSortNumber => { type => 'int', documentation => 'Number to sort buttons', }, casSrvMetaDataOptionsResolutionRule => { type => 'longtext', default => '', }, # Fake attribute: used by manager REST API to agglomerate all nodes # related to a CAS IDP partner casSrvMetaDataNodes => { type => 'casSrvMetaDataNodeContainer', template => 'casSrvMetaDataNode', help => 'authcas.html', }, # PAM pamAuthnLevel => { type => 'int', default => 2, documentation => 'PAM authentication level', }, pamService => { type => 'text', default => 'login', documentation => 'PAM service', }, # GPG gpgDb => { type => 'text', default => '', documentation => 'GPG keys database', }, gpgAuthnLevel => { type => 'int', default => 5, documentation => 'GPG authentication level', }, # Radius radiusAuthnLevel => { type => 'int', default => 3, documentation => 'Radius authentication level', }, radiusSecret => { type => 'text' }, radiusServer => { type => 'text' }, # REST restAuthUrl => { type => 'url' }, restUserDBUrl => { type => 'url' }, restFindUserDBUrl => { type => 'url' }, restPwdConfirmUrl => { type => 'url' }, restPwdModifyUrl => { type => 'url' }, # Remote remoteCookieName => { type => 'text', test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/, msgFail => '__badCookieName__', documentation => 'Name of the remote portal cookie', flags => 'p', }, remotePortal => { type => 'text' }, remoteGlobalStorage => { type => 'PerlModule', default => 'Lemonldap::NG::Common::Apache::Session::SOAP', documentation => 'Remote session backend', }, remoteGlobalStorageOptions => { type => 'keyTextContainer', default => { proxy => 'http://auth.example.com/sessions', ns => 'http://auth.example.com/Lemonldap/NG/Common/PSGI/SOAPService', }, documentation => 'Apache::Session module parameters', }, # Proxy proxyAuthService => { type => 'text' }, proxySessionService => { type => 'text' }, proxyAuthServiceChoiceValue => { type => 'text' }, proxyAuthServiceChoiceParam => { type => 'text', default => 'lmAuth' }, proxyAuthServiceImpersonation => { type => 'bool', default => 0, documentation => 'Enable internal portal Impersonation', }, proxyCookieName => { type => 'text', test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/, msgFail => '__badCookieName__', documentation => 'Name of the internal portal cookie', flags => 'p', }, proxyUseSoap => { type => 'bool', default => 0, documentation => 'Use SOAP instead of REST', }, proxyAuthnLevel => { type => 'int', default => 2, documentation => 'Proxy authentication level', }, # OpenID openIdAuthnLevel => { type => 'int', default => 1, documentation => 'OpenID authentication level', }, openIdSecret => { type => 'text', }, openIdExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'OpenID exported variables', }, 'openIdIDPList' => { 'type' => 'blackWhiteList', default => '0;' }, # Facebook facebookAuthnLevel => { type => 'int', default => 1, documentation => 'Facebook authentication level', }, facebookAppId => { type => 'text', }, facebookAppSecret => { type => 'text', }, facebookExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'Facebook exported variables', }, facebookUserField => { type => 'text', default => 'id' }, # Twitter twitterAuthnLevel => { type => 'int', default => 1, documentation => 'Twitter authentication level', }, twitterKey => { type => 'text', }, twitterSecret => { type => 'text', }, twitterAppName => { type => 'text', }, twitterUserField => { type => 'text', default => 'screen_name' }, # LinkedIn linkedInAuthnLevel => { type => 'int', default => 1, documentation => 'LinkedIn authentication level', }, linkedInClientID => { type => 'text', }, linkedInClientSecret => { type => 'password', }, linkedInFields => { type => 'text', default => 'id,first-name,last-name,email-address' }, linkedInUserField => { type => 'text', default => 'emailAddress' }, linkedInScope => { type => 'text', default => 'r_liteprofile r_emailaddress' }, # GitHub githubAuthnLevel => { type => 'int', default => 1, documentation => 'GitHub authentication level', }, githubClientID => { type => 'text', }, githubClientSecret => { type => 'password', }, githubScope => { type => 'text', default => 'user:email' }, githubUserField => { type => 'text', default => 'login' }, # WebID webIDAuthnLevel => { type => 'int', default => 1, documentation => 'WebID authentication level', }, webIDWhitelist => { type => 'text', }, webIDExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'WebID exported variables', }, # DBI dbiAuthnLevel => { type => 'int', default => 2, documentation => 'DBI authentication level', }, dbiAuthChain => { type => 'text', }, dbiAuthUser => { type => 'text', }, dbiAuthPassword => { type => 'password', }, dbiUserChain => { type => 'text', }, dbiUserUser => { type => 'text', }, dbiUserPassword => { type => 'password', }, dbiAuthTable => { type => 'text', }, dbiUserTable => { type => 'text', }, # TODO: add dbiMailCol dbiAuthLoginCol => { type => 'text', }, dbiAuthPasswordCol => { type => 'text', }, dbiPasswordMailCol => { type => 'text', }, userPivot => { type => 'text', }, dbiAuthPasswordHash => { type => 'text', help => 'authdbi.html#password', }, dbiDynamicHashEnabled => { type => 'bool', help => 'authdbi.html#password', }, dbiDynamicHashValidSchemes => { type => 'text', help => 'authdbi.html#password', }, dbiDynamicHashValidSaltedSchemes => { type => 'text', help => 'authdbi.html#password', }, dbiDynamicHashNewPasswordScheme => { type => 'text', help => 'authdbi.html#password', }, dbiExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'DBI exported variables', }, # Apache apacheAuthnLevel => { type => 'int', default => 3, documentation => 'Apache authentication level', }, # Null nullAuthnLevel => { type => 'int', default => 0, documentation => 'Null authentication level', }, # Kerberos krbKeytab => { type => 'text', documentation => 'Kerberos keytab', }, krbByJs => { type => 'bool', default => 0, documentation => 'Launch Kerberos authentication by Ajax', }, krbAuthnLevel => { type => 'int', default => 3, documentation => 'Null authentication level', }, krbRemoveDomain => { type => 'bool', default => 1, documentation => 'Remove domain in Kerberos username', }, krbAllowedDomains => { type => 'text', documentation => 'Allowed domains', }, # Slave slaveAuthnLevel => { type => 'int', default => 2, documentation => 'Slave authentication level', }, slaveUserHeader => { type => 'text', }, slaveExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'Slave exported variables', }, slaveMasterIP => { type => 'text', test => qr/^($Regexp::Common::URI::RFC2396::IPv4address\s*)*$/, msgFail => '__badIPv4Address__', }, slaveHeaderName => { type => 'text', }, slaveHeaderContent => { type => 'text', }, slaveDisplayLogo => { type => 'bool', default => 0, documentation => 'Display Slave authentication logo', }, # Choice authChoiceParam => { type => 'text', default => 'lmAuth', documentation => 'Applications list', }, authChoiceAuthBasic => { type => 'text', documentation => 'Auth module used by AuthBasic handler', }, authChoiceFindUser => { type => 'text', documentation => 'Auth module used by FindUser plugin', }, authChoiceModules => { type => 'authChoiceContainer', keyTest => qr/^(\d*)?[a-zA-Z0-9_]+$/, keyMsgFail => '__badChoiceKey__', test => sub { 1 }, select => [ [ { k => 'Apache', v => 'Apache' }, { k => 'AD', v => 'Active Directory' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demo' }, { k => 'Facebook', v => 'Facebook' }, { k => 'GitHub', v => 'GitHub' }, { k => 'GPG', v => 'GPG' }, { k => 'Kerberos', v => 'Kerberos' }, { k => 'LDAP', v => 'LDAP' }, { k => 'LinkedIn', v => 'LinkedIn' }, { k => 'PAM', v => 'PAM' }, { k => 'Null', v => 'None' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Radius', v => 'Radius' }, { k => 'REST', v => 'REST' }, { k => 'Remote', v => 'Remote' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Slave', v => 'Slave' }, { k => 'SSL', v => 'SSL' }, { k => 'Twitter', v => 'Twitter' }, { k => 'WebID', v => 'WebID' }, { k => 'Custom', v => 'customModule' }, ], [ { k => 'AD', v => 'Active Directory' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demo' }, { k => 'Facebook', v => 'Facebook' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Null', v => 'None' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'Proxy', v => 'Proxy' }, { k => 'REST', v => 'REST' }, { k => 'Remote', v => 'Remote' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Slave', v => 'Slave' }, { k => 'WebID', v => 'WebID' }, { k => 'Custom', v => 'customModule' }, ], [ { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demo' }, { k => 'LDAP', v => 'LDAP' }, { k => 'REST', v => 'REST' }, { k => 'Null', v => 'None' }, { k => 'Custom', v => 'customModule' }, ] ], documentation => 'Hash list of Choice strings', }, # Combination combination => { type => 'text', documentation => 'Combination rule' }, combModules => { type => 'cmbModuleContainer', keyTest => qr/^\w+$/, test => sub { 1 }, documentation => 'Combination module description', select => [ { k => 'Apache', v => 'Apache' }, { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Facebook', v => 'Facebook' }, { k => 'GitHub', v => 'GitHub' }, { k => 'GPG', v => 'GPG' }, { k => 'Kerberos', v => 'Kerberos' }, { k => 'LDAP', v => 'LDAP' }, { k => 'LinkedIn', v => 'LinkedIn' }, { k => 'PAM', v => 'PAM' }, { k => 'Radius', v => 'Radius' }, { k => 'REST', v => 'REST' }, { k => 'SSL', v => 'SSL' }, { k => 'Twitter', v => 'Twitter' }, { k => 'WebID', v => 'WebID' }, { k => 'Demo', v => 'Demonstration' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Remote', v => 'Remote' }, { k => 'Slave', v => 'Slave' }, { k => 'Null', v => 'None' }, { k => 'Custom', v => 'customModule' }, ], }, sfExtra => { type => 'sfExtraContainer', keyTest => qr/^\w+$/, test => sub { 1 }, documentation => 'Extra second factors', select => [ { k => 'Mail2F', v => 'E-Mail' }, { k => 'REST', v => 'REST' }, { k => 'Ext2F', v => 'External' }, { k => 'Radius', v => 'Radius' }, ], }, # Custom auth modules customAuth => { type => 'text', documentation => 'Custom auth module', }, customUserDB => { type => 'text', documentation => 'Custom user DB module', }, customPassword => { type => 'text', documentation => 'Custom password module', }, customRegister => { type => 'text', documentation => 'Custom register module', }, customResetCertByMail => { type => 'text', documentation => 'Custom certificateResetByMail module', }, customAddParams => { type => 'keyTextContainer', documentation => 'Custom additional parameters', }, # Custom plugins customPlugins => { type => 'text', documentation => 'Custom plugins', }, customPluginsParams => { type => 'keyTextContainer', documentation => 'Custom plugins parameters', }, # OpenID Connect auth params oidcAuthnLevel => { type => 'int', default => 1, documentation => 'OpenID Connect authentication level', }, oidcRPCallbackGetParam => { type => 'text', default => 'openidconnectcallback', documentation => 'OpenID Connect Callback GET URLparameter', }, oidcRPStateTimeout => { type => 'int', default => 600, documentation => 'OpenID Connect Timeout of state sessions', }, # OpenID Connect service oidcServiceMetaDataIssuer => { type => 'text', documentation => 'OpenID Connect issuer', }, oidcServiceMetaDataAuthorizeURI => { type => 'text', default => 'authorize', documentation => 'OpenID Connect authorizaton endpoint', }, oidcServiceMetaDataTokenURI => { type => 'text', default => 'token', documentation => 'OpenID Connect token endpoint', }, oidcServiceMetaDataUserInfoURI => { type => 'text', default => 'userinfo', documentation => 'OpenID Connect user info endpoint', }, oidcServiceMetaDataJWKSURI => { type => 'text', default => 'jwks', documentation => 'OpenID Connect JWKS endpoint', }, oidcServiceMetaDataRegistrationURI => { type => 'text', default => 'register', documentation => 'OpenID Connect registration endpoint', }, oidcServiceMetaDataIntrospectionURI => { type => 'text', default => 'introspect', documentation => 'OpenID Connect introspection endpoint', }, oidcServiceMetaDataEndSessionURI => { type => 'text', default => 'logout', documentation => 'OpenID Connect end session endpoint', }, oidcServiceMetaDataCheckSessionURI => { type => 'text', default => 'checksession.html', documentation => 'OpenID Connect check session iframe', }, oidcServiceMetaDataBackChannelURI => { type => 'text', default => 'blogout', documentation => 'OpenID Connect Front-Channel logout endpoint', }, oidcServiceMetaDataFrontChannelURI => { type => 'text', default => 'flogout', documentation => 'OpenID Connect Front-Channel logout endpoint', }, oidcServiceMetaDataAuthnContext => { type => 'keyTextContainer', keyTest => qr/\w/, default => { 'loa-1' => 1, 'loa-2' => 2, 'loa-3' => 3, 'loa-4' => 4, 'loa-5' => 5, }, documentation => 'OpenID Connect Authentication Context Class Ref', }, oidcServicePrivateKeySig => { type => 'RSAPrivateKey', }, oidcServicePublicKeySig => { type => 'RSAPublicKey', }, oidcServiceKeyIdSig => { type => 'text', documentation => 'OpenID Connect Signature Key ID', }, oidcServiceAllowDynamicRegistration => { type => 'bool', default => 0, documentation => 'OpenID Connect allow dynamic client registration', }, oidcServiceAllowOnlyDeclaredScopes => { type => 'bool', default => 0, documentation => 'OpenID Connect allow only declared scopes', }, oidcServiceAllowAuthorizationCodeFlow => { type => 'bool', default => 1, documentation => 'OpenID Connect allow authorization code flow', }, oidcServiceAllowImplicitFlow => { type => 'bool', default => 0, documentation => 'OpenID Connect allow implicit flow', }, oidcServiceAllowHybridFlow => { type => 'bool', default => 0, documentation => 'OpenID Connect allow hybrid flow', }, oidcServiceAuthorizationCodeExpiration => { type => 'int', default => 60, documentation => 'OpenID Connect global code TTL', }, oidcServiceAccessTokenExpiration => { type => 'int', default => 3600, documentation => 'OpenID Connect global access token TTL', }, oidcServiceDynamicRegistrationExportedVars => { type => 'keyTextContainer', documentation => 'OpenID Connect exported variables for dynamic registration', }, oidcServiceDynamicRegistrationExtraClaims => { type => 'keyTextContainer', keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/, documentation => 'OpenID Connect extra claims for dynamic registration', }, oidcServiceIDTokenExpiration => { type => 'int', default => 3600, documentation => 'OpenID Connect global ID token TTL', }, oidcServiceOfflineSessionExpiration => { type => 'int', default => 2592000, documentation => 'OpenID Connect global offline session TTL', }, oidcStorage => { type => 'PerlModule', documentation => 'Apache::Session module to store OIDC user data', }, oidcStorageOptions => { type => 'keyTextContainer', documentation => 'Apache::Session module parameters', }, # OpenID Connect metadata nodes oidcOPMetaDataNodes => { type => 'oidcOPMetaDataNodeContainer', help => 'authopenidconnect.html#declare-the-openid-connect-provider-in-ll-ng', }, oidcRPMetaDataNodes => { type => 'oidcRPMetaDataNodeContainer', help => 'idpopenidconnect.html#configuration-of-relying-party-in-ll-ng', }, oidcOPMetaDataOptions => { type => 'subContainer', }, oidcRPMetaDataOptions => { type => 'subContainer', }, # OpenID Connect providers oidcOPMetaDataJSON => { type => 'file', keyTest => sub { 1 } }, oidcOPMetaDataJWKS => { type => 'file', keyTest => sub { 1 } }, oidcOPMetaDataExportedVars => { type => 'keyTextContainer', default => { 'cn' => 'name', 'sn' => 'family_name', 'mail' => 'email', 'uid' => 'sub' } }, oidcOPMetaDataOptionsConfigurationURI => { type => 'url', }, oidcOPMetaDataOptionsJWKSTimeout => { type => 'int', default => 0 }, oidcOPMetaDataOptionsClientID => { type => 'text', }, oidcOPMetaDataOptionsClientSecret => { type => 'password', }, oidcOPMetaDataOptionsScope => { type => 'text', default => 'openid profile' }, oidcOPMetaDataOptionsDisplay => { type => 'select', select => [ { k => '', v => '' }, { k => 'page', v => 'page' }, { k => 'popup', v => 'popup' }, { k => 'touch', v => 'touch' }, { k => 'wap', v => 'wap' }, ], default => "", }, oidcOPMetaDataOptionsPrompt => { type => 'text' }, oidcOPMetaDataOptionsMaxAge => { type => 'int', default => 0 }, oidcOPMetaDataOptionsUiLocales => { type => 'text', }, oidcOPMetaDataOptionsAcrValues => { type => 'text', }, oidcOPMetaDataOptionsTokenEndpointAuthMethod => { type => 'select', select => [ { k => 'client_secret_post', v => 'client_secret_post' }, { k => 'client_secret_basic', v => 'client_secret_basic' }, ], default => 'client_secret_post', }, oidcOPMetaDataOptionsCheckJWTSignature => { type => 'bool', default => 1 }, oidcOPMetaDataOptionsIDTokenMaxAge => { type => 'int', default => 30 }, oidcOPMetaDataOptionsUseNonce => { type => 'bool', default => 1 }, oidcOPMetaDataOptionsDisplayName => { type => 'text', }, oidcOPMetaDataOptionsIcon => { type => 'text', }, oidcOPMetaDataOptionsStoreIDToken => { type => 'bool', default => 0 }, oidcOPMetaDataOptionsSortNumber => { type => 'int', }, oidcOPMetaDataOptionsResolutionRule => { type => 'longtext', default => '', }, # OpenID Connect relying parties oidcRPMetaDataExportedVars => { help => 'idpopenidconnect.html#oidcexportedattr', type => 'oidcAttributeContainer', keyTest => qr/\w/, test => qr/\w/, default => { 'name' => 'cn', 'family_name' => 'sn', 'email' => 'mail', } }, oidcRPMetaDataOptionsClientID => { type => 'text', }, oidcRPMetaDataOptionsClientSecret => { type => 'password', }, oidcRPMetaDataOptionsDisplayName => { type => 'text', }, oidcRPMetaDataOptionsIcon => { type => 'text', }, oidcRPMetaDataOptionsUserIDAttr => { type => 'text', }, oidcRPMetaDataOptionsIDTokenSignAlg => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'HS256', v => 'HS256' }, { k => 'HS384', v => 'HS384' }, { k => 'HS512', v => 'HS512' }, { k => 'RS256', v => 'RS256' }, { k => 'RS384', v => 'RS384' }, { k => 'RS512', v => 'RS512' }, ], default => 'HS512', }, oidcRPMetaDataOptionsIDTokenExpiration => { type => 'int' }, oidcRPMetaDataOptionsIDTokenForceClaims => { type => 'bool', default => 0 }, oidcRPMetaDataOptionsAccessTokenSignAlg => { type => 'select', select => [ { k => 'RS256', v => 'RS256' }, { k => 'RS384', v => 'RS384' }, { k => 'RS512', v => 'RS512' }, ], default => 'RS256', }, oidcRPMetaDataOptionsUserInfoSignAlg => { type => 'select', select => [ { k => '', v => 'JSON' }, { k => 'none', v => 'JWT/None' }, { k => 'HS256', v => 'JWT/HS256' }, { k => 'HS384', v => 'JWT/HS384' }, { k => 'HS512', v => 'JWT/HS512' }, { k => 'RS256', v => 'JWT/RS256' }, { k => 'RS384', v => 'JWT/RS384' }, { k => 'RS512', v => 'JWT/RS512' }, ], default => '', }, oidcRPMetaDataOptionsAccessTokenJWT => { type => 'bool', default => 0 }, oidcRPMetaDataOptionsAccessTokenClaims => { type => 'bool', default => 0 }, oidcRPMetaDataOptionsAdditionalAudiences => { type => 'text' }, oidcRPMetaDataOptionsAccessTokenExpiration => { type => 'int' }, oidcRPMetaDataOptionsAuthorizationCodeExpiration => { type => 'int' }, oidcRPMetaDataOptionsOfflineSessionExpiration => { type => 'int' }, oidcRPMetaDataOptionsRedirectUris => { type => 'text', }, oidcRPMetaDataOptionsExtraClaims => { type => 'keyTextContainer', keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/, default => {}, help => 'idpopenidconnect.html#oidcextraclaims' }, oidcRPMetaDataOptionsBypassConsent => { type => 'bool', default => 0 }, oidcRPMetaDataOptionsPostLogoutRedirectUris => { type => 'text', }, oidcRPMetaDataOptionsLogoutBypassConfirm => { type => 'bool', default => 0, documentation => 'Bypass logout confirmation' }, oidcRPMetaDataOptionsLogoutUrl => { type => 'url', documentation => 'Logout URL', }, oidcRPMetaDataOptionsLogoutType => { type => 'select', select => [ { k => 'front', v => 'Front Channel' }, #TODO #1194 # { k => 'back', v => 'Back Channel' }, ], default => 'front', documentation => 'Logout type', }, oidcRPMetaDataOptionsLogoutSessionRequired => { type => 'bool', default => 0, documentation => 'Session required for logout', }, oidcRPMetaDataOptionsPublic => { type => 'bool', default => 0, documentation => 'Declare this RP as public client', }, oidcRPMetaDataOptionsRequirePKCE => { type => 'bool', default => 0, documentation => 'Require PKCE', }, oidcRPMetaDataOptionsAllowOffline => { type => 'bool', default => 0, documentation => 'Allow offline access', }, oidcRPMetaDataOptionsAllowPasswordGrant => { type => 'bool', default => 0, documentation => 'Allow OAuth2 Resource Owner Password Credentials Grant', }, oidcRPMetaDataOptionsAllowClientCredentialsGrant => { type => 'bool', default => 0, documentation => 'Allow OAuth2 Client Credentials Grant', }, oidcRPMetaDataOptionsRefreshToken => { type => 'bool', default => 0, documentation => 'Issue refresh tokens', }, oidcRPMetaDataOptionsAuthnLevel => { type => 'int', documentation => 'Authentication level requires to access to this RP', }, oidcRPMetaDataOptionsRule => { type => 'text', test => sub { return perlExpr(@_) }, documentation => 'Rule to grant access to this RP', }, oidcRPMetaDataMacros => { type => 'keyTextContainer', help => 'exportedvars.html#extend-variables-using-macros-and-groups', test => { keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badMacroName__', test => sub { return perlExpr(@_) }, }, default => {}, documentation => 'Macros', }, oidcRPMetaDataScopeRules => { type => 'keyTextContainer', help => 'idpopenidconnect.html#scope-rules', test => { # RFC6749 keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/, keyMsgFail => '__badMacroName__', test => sub { return perlExpr(@_) }, }, default => {}, documentation => 'Scope rules', }, }; } 1;