From e41443838011742c17f92b00676415114db6a70a Mon Sep 17 00:00:00 2001 From: Lionel Elie Mamane Date: Tue, 9 Feb 2021 18:55:13 +0100 Subject: [PATCH] introduce a whitelist of uids exempted from ip check --- Makefile | 4 +- appinfo/app.php | 19 +++---- appinfo/info.xml | 19 ++++--- css/settings.scss | 26 +++++++++- js/settings.js | 97 +++++++++++++++++++++++++++++++++--- lib/LoginHookListener.php | 24 ++++++++- templates/admin-settings.php | 12 +++++ 7 files changed, 170 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 433ff38..406d8ec 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ source_dir=$(build_dir)/source sign_dir=$(build_dir)/sign package_name=$(app_name) cert_dir=$(HOME)/.nextcloud/certificates -version+=1.0.2 +version+=3.1.0 all: appstore @@ -47,4 +47,4 @@ appstore: clean @if [ -f $(cert_dir)/$(app_name).key ]; then \ echo "Signing packageā€¦"; \ openssl dgst -sha512 -sign $(cert_dir)/$(app_name).key $(build_dir)/$(app_name)-$(version).tar.gz | openssl base64; \ - fi \ No newline at end of file + fi diff --git a/appinfo/app.php b/appinfo/app.php index ecf9be9..14f86f9 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -37,16 +37,9 @@ $isLoginpage ); -if(!$loginHookListener->isLoginAllowed()) { - if($isLoginpage) { - header('Location: ' . \OC::$WEBROOT . '/index.php/apps/limit_login_to_ip/denied'); - exit(); - } - - \OCP\Util::connectHook( - 'OC_User', - 'pre_login', - $loginHookListener, - 'handleLoginRequest' - ); -} +\OCP\Util::connectHook( + 'OC_User', + 'pre_login', + $loginHookListener, + 'handleLoginRequest' +); diff --git a/appinfo/info.xml b/appinfo/info.xml index f759f6e..9bc7f3e 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -2,15 +2,16 @@ limit_login_to_ip - Restrict login to IP addresses - Allows administrators to restrict logins to their instance to specific IP ranges. + Restrict logins to whitelisted IP addresses or users + Allows administrators to restrict logins to their instance by user or specific IP ranges. 3.1.0 agpl diff --git a/css/settings.scss b/css/settings.scss index 32f1e13..34f460f 100644 --- a/css/settings.scss +++ b/css/settings.scss @@ -20,4 +20,28 @@ #limit-login-to-ip-list .action-column a { display: inline-block; -} \ No newline at end of file +} + +#limit-login-to-ip-uidlist-spinner { + margin-top: 25px; + margin-bottom: 25px; + margin-left: auto; + margin-right: auto; +} + +#limit-login-to-ip-uidlist { + min-width: 262px; +} + +#limit-login-to-ip-uidlist td span { + padding: 10px 15px; + display: inline-block; +} + +#limit-login-to-ip-uidlist .action-column { + width: 46px; +} + +#limit-login-to-ip-uidlist .action-column a { + display: inline-block; +} diff --git a/js/settings.js b/js/settings.js index 93e961c..02c2162 100644 --- a/js/settings.js +++ b/js/settings.js @@ -21,6 +21,7 @@ (function(OCA) { OCA.LimitLoginToIp = OCA.LimitLoginToIp || {}; OCA.LimitLoginToIp.Ranges = []; + OCA.LimitLoginToIp.UIDs = []; /** * @namespace OCA.LimitLoginToIp.Settings @@ -58,7 +59,42 @@ actionCell.innerHTML = actionCellValue.outerHTML; }); - OCA.LimitLoginToIp.Settings.setEnabledState(true); + OCA.LimitLoginToIp.Settings.setIPEnabledState(true); + } + } + ); + OCP.AppConfig.getValue( + 'limit_login_to_ip', + 'whitelisted.uids', + '', + { + success: function(data) { + var textData = $(data).find('data data').text(); + var UIDs = textData.split(','); + var table = document.getElementById('limit-login-to-ip-uidlist'); + table.innerHTML = ''; + + OCA.LimitLoginToIp.UIDs = UIDs; + UIDs.forEach(function(uid) { + var row = table.insertRow(0); + var actionCell = row.insertCell(0); + actionCell.className = 'action-column'; + var uidCell = row.insertCell(0); + + var uidCellValue = document.createElement('span'); + uidCellValue.innerText = uid; + uidCell.innerHTML = uidCellValue.outerHTML; + + var actionCellValue = document.createElement('span'); + var deleteLink = document.createElement('a'); + deleteLink.className = 'icon-delete has-tooltip'; + deleteLink.title = t('limit_login_to_ip', 'Delete'); + deleteLink.setAttribute('data', uid); + actionCellValue.innerHTML = deleteLink.outerHTML; + actionCell.innerHTML = actionCellValue.outerHTML; + }); + + OCA.LimitLoginToIp.Settings.setUIDEnabledState(true); } } ); @@ -89,7 +125,32 @@ OCA.LimitLoginToIp.Settings.storeRanges(); }, - setEnabledState: function(isEnabled) { + storeUIDs: function() { + var uids = OCA.LimitLoginToIp.UIDs.join(); + OCP.AppConfig.setValue( + 'limit_login_to_ip', + 'whitelisted.uids', + uids, + { + success: function () { + OCA.LimitLoginToIp.Settings.load(); + } + } + ); + }, + + addUID: function(uid) { + OCA.LimitLoginToIp.UIDs.push(uid); + OCA.LimitLoginToIp.Settings.storeUIDs(); + }, + + removeUID: function(uid) { + var index = OCA.LimitLoginToIp.UIDs.indexOf(uid); + OCA.LimitLoginToIp.UIDs.splice(index, 1); + OCA.LimitLoginToIp.Settings.storeUIDs(); + }, + + setIPEnabledState: function(isEnabled) { if(isEnabled !== true) { $('#limit-login-to-ip-list-spinner').removeClass('hidden'); } else { @@ -97,6 +158,16 @@ } $('#limit-login-to-ip-input-fields input').prop('disabled', !isEnabled); + }, + + setUIDEnabledState: function(isEnabled) { + if(isEnabled !== true) { + $('#limit-login-to-ip-uidlist-spinner').removeClass('hidden'); + } else { + $('#limit-login-to-ip-uidlist-spinner').addClass('hidden'); + } + + $('#limit-login-to-ip-uid-input-fields input').prop('disabled', !isEnabled); } }; @@ -108,7 +179,7 @@ $('#limit-login-to-ip-submit').click(function() { var ipAddress = $('#limit-login-to-ip-whitelist').val(); var range = $('#limit-login-to-ip-whitelist_mask').val(); - + // ipAddress validation // https://www.regexpal.com/?fam=104038 var regexFilter = '((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))'; @@ -118,8 +189,8 @@ if ( regexMatch == null ) { return false; } - - OCA.LimitLoginToIp.Settings.setEnabledState(false); + + OCA.LimitLoginToIp.Settings.setIPEnabledState(false); OCA.LimitLoginToIp.Settings.addRange(ipAddress + '/' + range); $('#limit-login-to-ip-whitelist').val(''); $('#limit-login-to-ip-whitelist_mask').val(); @@ -127,7 +198,21 @@ $('#limit-login-to-ip-list').on('click', 'a', function() { var rangeToRemove = $(this).attr('data'); - OCA.LimitLoginToIp.Settings.setEnabledState(false); + OCA.LimitLoginToIp.Settings.setIPEnabledState(false); OCA.LimitLoginToIp.Settings.removeRange(rangeToRemove); }); + + $('#limit-login-to-ip-uid-submit').click(function() { + var uid = $('#limit-login-to-ip-uid-whitelist').val(); + + OCA.LimitLoginToIp.Settings.setUIDEnabledState(false); + OCA.LimitLoginToIp.Settings.addUID(uid); + $('#limit-login-to-ip-uid-whitelist').val(''); + }); + + $('#limit-login-to-ip-uidlist').on('click', 'a', function() { + var uidToRemove = $(this).attr('data'); + OCA.LimitLoginToIp.Settings.setUIDEnabledState(false); + OCA.LimitLoginToIp.Settings.removeUID(uidToRemove); + }); })(); diff --git a/lib/LoginHookListener.php b/lib/LoginHookListener.php index eb402ed..bfc1ed0 100644 --- a/lib/LoginHookListener.php +++ b/lib/LoginHookListener.php @@ -108,7 +108,7 @@ private function matchCidr($ip, $range) { /** * @return bool */ - public function isLoginAllowed() { + public function isIPWhiteListed() { $allowedRanges = $this->config->getAppValue('limit_login_to_ip', 'whitelisted.ranges', ''); if($allowedRanges === '') { return true; @@ -125,7 +125,27 @@ public function isLoginAllowed() { return false; } - public function handleLoginRequest() { + /** + * @return bool + */ + public function isUidWhiteListed($uid) { + $allowedUids = $this->config->getAppValue('limit_login_to_ip', 'whitelisted.uids', ''); + if($allowedUids === '') { + return false; + } + + $allowedUids = explode(',', $allowedUids); + + return in_array($uid, $allowedUids, true); + } + + public function handleLoginRequest(array $params) { + if ( !$this->isUidWhiteListed($params['uid']) && !$this->isIPWhiteListed() ) { + $this->denyLoginRequest(); + } + } + + public function denyLoginRequest() { // Web UI if($this->isLoginPage) { $url = $this->urlGenerator->linkToRouteAbsolute('limit_login_to_ip.LoginDenied.showErrorPage'); diff --git a/templates/admin-settings.php b/templates/admin-settings.php index e6c383c..1b31739 100644 --- a/templates/admin-settings.php +++ b/templates/admin-settings.php @@ -38,4 +38,16 @@ + + t('These users can connect from any IP address. These are matched against the authentication username.')) ?> + +
+ + +
+ +
+ + +