From 0d36d43edac9b66bdded00701df9e1289dab88aa Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 13 Oct 2018 15:06:56 +0200 Subject: [PATCH] Initial code for LDAP authentication. --- .env.example | 43 +++ .../Auth/ForgotPasswordController.php | 14 + app/Http/Controllers/Auth/LoginController.php | 14 +- .../Controllers/Auth/RegisterController.php | 40 ++- .../Auth/ResetPasswordController.php | 49 ++- app/Http/Controllers/ProfileController.php | 52 ++- bootstrap/app.php | 2 +- composer.json | 3 + composer.lock | 127 ++++++- config/adldap.php | 259 ++++++++++++++ config/adldap_auth.php | 317 ++++++++++++++++++ config/auth.php | 8 +- config/firefly.php | 1 + resources/lang/en_US/firefly.php | 2 + resources/lang/en_US/form.php | 1 + resources/views/auth/login.twig | 10 +- 16 files changed, 905 insertions(+), 37 deletions(-) create mode 100644 config/adldap.php create mode 100644 config/adldap_auth.php diff --git a/.env.example b/.env.example index e53df1a9ca..3a450e42b8 100644 --- a/.env.example +++ b/.env.example @@ -92,9 +92,52 @@ ANALYTICS_ID= # This makes it easier to migrate your database. Not that some fields will never be decrypted. USE_ENCRYPTION=true +# Firefly III has two options for user authentication. "eloquent" is the default, +# and "adldap" for LDAP servers. +# For full instructions on these settings please visit: +# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html +LOGIN_PROVIDER=eloquent + +# LDAP connection configuration +ADLDAP_CONNECTION_SCHEME=OpenLDAP # or FreeIPA or ActiveDirectory +ADLDAP_AUTO_CONNECT=true + +# LDAP connection settings +ADLDAP_CONTROLLERS= +ADLDAP_PORT=389 +ADLDAP_TIMEOUT=5 +ADLDAP_BASEDN="" +ADLDAP_FOLLOW_REFFERALS=false +ADLDAP_USE_SSL=false +ADLDAP_USE_TLS=false + +ADLDAP_ADMIN_USERNAME= +ADLDAP_ADMIN_PASSWORD= + +ADLDAP_ACCOUNT_PREFIX= +ADLDAP_ACCOUNT_SUFFIX= +ADLDAP_ADMIN_ACCOUNT_PREFIX= +ADLDAP_ADMIN_ACCOUNT_SUFFIX= + +# LDAP authentication settings. +ADLDAP_PASSWORD_SYNC=false +ADLDAP_LOGIN_FALLBACK=false + +ADLDAP_SYNC_FIELD=userprincipalname +ADLDAP_DISCOVER_FIELD=distinguishedname +ADLDAP_AUTH_FIELD=distinguishedname + +# Will allow SSO if your server provides an AUTH_USER field. +WINDOWS_SSO_DISCOVER=samaccountname +WINDOWS_SSO_KEY=AUTH_USER + +# field to sync as local username. +ADLDAP_SYNC_FIELD=uid + # Leave the following configuration vars as is. # Unless you like to tinker and know what you're doing. APP_NAME=FireflyIII +ADLDAP_CONNECTION=default BROADCAST_DRIVER=log QUEUE_DRIVER=sync REDIS_HOST=127.0.0.1 diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index ccdb13e75c..466c0dc67e 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -58,6 +58,13 @@ class ForgotPasswordController extends Controller */ public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository) { + $loginProvider = getenv('LOGIN_PROVIDER'); + if ('eloquent' !== $loginProvider) { + $message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider); + + return view('error', compact('message')); + } + $this->validateEmail($request); // verify if the user is not a demo user. If so, we give him back an error. @@ -90,6 +97,13 @@ class ForgotPasswordController extends Controller */ public function showLinkRequestForm() { + $loginProvider = getenv('LOGIN_PROVIDER'); + if ('eloquent' !== $loginProvider) { + $message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider); + + return view('error', compact('message')); + } + // is allowed to? $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; $userCount = User::count(); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 0ca4c9f6b7..6d57c321b6 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -129,8 +129,9 @@ class LoginController extends Controller */ public function showLoginForm(Request $request) { - $count = DB::table('users')->count(); - if (0 === $count) { + $count = DB::table('users')->count(); + $loginProvider = getenv('LOGIN_PROVIDER'); + if (0 === $count && 'eloquent' === $loginProvider) { return redirect(route('register')); // @codeCoverageIgnore } @@ -141,13 +142,20 @@ class LoginController extends Controller $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; $userCount = User::count(); $allowRegistration = true; + $allowReset = true; if (true === $singleUserMode && $userCount > 0) { $allowRegistration = false; } + // single user mode is ignored when the user is not using eloquent: + if ('eloquent' !== $loginProvider) { + $allowRegistration = false; + $allowReset = false; + } + $email = $request->old('email'); $remember = $request->old('remember'); - return view('auth.login', compact('allowRegistration', 'email', 'remember')); + return view('auth.login', compact('allowRegistration', 'email', 'remember','allowReset')); } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 2ece879132..7976eb5d62 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -71,9 +71,19 @@ class RegisterController extends Controller public function register(Request $request) { // is allowed to? - $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; - $userCount = User::count(); - if (true === $singleUserMode && $userCount > 0) { + $allowRegistration = true; + $loginProvider = getenv('LOGIN_PROVIDER'); + $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; + $userCount = User::count(); + if (true === $singleUserMode && $userCount > 0 && 'eloquent' === $loginProvider) { + $allowRegistration = false; + } + + if ('eloquent' !== $loginProvider) { + $allowRegistration = false; + } + + if (false === $allowRegistration) { $message = 'Registration is currently not available.'; return view('error', compact('message')); @@ -102,13 +112,25 @@ class RegisterController extends Controller */ public function showRegistrationForm(Request $request) { - // is demo site? - $isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; + $allowRegistration = true; + $loginProvider = getenv('LOGIN_PROVIDER'); + $isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; + $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; + $userCount = User::count(); - // is allowed to? - $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; - $userCount = User::count(); - if (true === $singleUserMode && $userCount > 0) { + if (true === $isDemoSite) { + $allowRegistration = false; + } + + if (true === $singleUserMode && $userCount > 0 && 'eloquent' === $loginProvider) { + $allowRegistration = false; + } + + if ('eloquent' !== $loginProvider) { + $allowRegistration = false; + } + + if (false === $allowRegistration) { $message = 'Registration is currently not available.'; return view('error', compact('message')); diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index a5401c488d..3f26235867 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -28,6 +28,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; /** * Class ResetPasswordController @@ -70,7 +71,15 @@ class ResetPasswordController extends Controller */ public function showResetForm(Request $request, $token = null) { - // is allowed to? + $loginProvider = getenv('LOGIN_PROVIDER'); + if ('eloquent' !== $loginProvider) { + $message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider); + + return view('error', compact('message')); + } + + + // is allowed to register? $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; $userCount = User::count(); $allowRegistration = true; @@ -83,4 +92,42 @@ class ResetPasswordController extends Controller ['token' => $token, 'email' => $request->email, 'allowRegistration' => $allowRegistration] ); } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse + * @throws \Illuminate\Validation\ValidationException + */ + public function reset(Request $request) + { + $loginProvider = getenv('LOGIN_PROVIDER'); + if ('eloquent' !== $loginProvider) { + $message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider); + + return view('error', compact('message')); + } + + $this->validate($request, $this->rules(), $this->validationErrorMessages()); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $response = $this->broker()->reset( + $this->credentials($request), function ($user, $password) { + $this->resetPassword($user, $password); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + return $response === Password::PASSWORD_RESET + ? $this->sendResetResponse($request, $response) + : $this->sendResetFailedResponse($request, $response); + } + + } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index a22196b051..755e2f4df1 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -41,6 +41,7 @@ use FireflyIII\User; use Google2FA; use Hash; use Illuminate\Contracts\Auth\Guard; +use Illuminate\Http\Request; use Illuminate\Support\Collection; use Laravel\Passport\ClientRepository; use Log; @@ -71,6 +72,7 @@ class ProfileController extends Controller return $next($request); } ); + $this->middleware(IsDemoUser::class)->except(['index']); $this->middleware(IsSandStormUser::class)->except('index'); } @@ -80,8 +82,15 @@ class ProfileController extends Controller * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function changeEmail() + public function changeEmail(Request $request) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider])); + + return redirect(route('profile.index')); + } + $title = auth()->user()->email; $email = auth()->user()->email; $subTitle = (string)trans('firefly.change_your_email'); @@ -95,8 +104,15 @@ class ProfileController extends Controller * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function changePassword() + public function changePassword(Request $request) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider])); + + return redirect(route('profile.index')); + } + $title = auth()->user()->email; $subTitle = (string)trans('firefly.change_your_password'); $subTitleIcon = 'fa-key'; @@ -132,6 +148,10 @@ class ProfileController extends Controller */ public function confirmEmailChange(UserRepositoryInterface $repository, string $token) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + throw new FireflyException('Cannot confirm email change when authentication provider is not local.'); + } // find preference with this token value. /** @var Collection $set */ $set = app('preferences')->findByName('email_change_confirm_token'); @@ -163,8 +183,12 @@ class ProfileController extends Controller * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function deleteAccount() + public function deleteAccount(Request $request) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + $request->session()->flash('warning', trans('firefly.delete_local_info_only', ['login_provider' => $loginProvider])); + } $title = auth()->user()->email; $subTitle = (string)trans('firefly.delete_account'); $subTitleIcon = 'fa-trash'; @@ -216,6 +240,7 @@ class ProfileController extends Controller */ public function index() { + $loginProvider = config('firefly.login_provider'); // check if client token thing exists (default one) $count = DB::table('oauth_clients') ->where('personal_access_client', 1) @@ -241,7 +266,7 @@ class ProfileController extends Controller $accessToken = app('preferences')->set('access_token', $token); } - return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA')); + return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA', 'loginProvider')); } /** @@ -254,6 +279,13 @@ class ProfileController extends Controller */ public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider])); + + return redirect(route('profile.index')); + } + /** @var User $user */ $user = auth()->user(); $newEmail = $request->string('email'); @@ -299,6 +331,13 @@ class ProfileController extends Controller */ public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider])); + + return redirect(route('profile.index')); + } + // the request has already validated both new passwords must be equal. $current = $request->get('current_password'); $new = $request->get('new_password'); @@ -396,6 +435,11 @@ class ProfileController extends Controller */ public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash) { + $loginProvider = config('firefly.login_provider'); + if ('eloquent' !== $loginProvider) { + throw new FireflyException('Cannot confirm email change when authentication provider is not local.'); + } + // find preference with this token value. $set = app('preferences')->findByName('email_change_undo_token'); $user = null; diff --git a/bootstrap/app.php b/bootstrap/app.php index 0abe08a8d8..a2f85126e0 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -46,7 +46,7 @@ if (!function_exists('envNonEmpty')) { function envNonEmpty(string $key, $default = null) { $result = env($key, $default); - if (is_string($result) && $result === '') { + if (is_string($result) && '' === $result) { $result = $default; } diff --git a/composer.json b/composer.json index 66d610c88b..aa51f3bb78 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,9 @@ "ext-intl": "*", "ext-xml": "*", "ext-zip": "*", + "ext-json": "*", + "ext-ldap": "*", + "adldap2/adldap2-laravel": "^4.0", "bacon/bacon-qr-code": "1.*", "bunq/sdk_php": "dev-master#8c1faefc111d9b970168a1837ca165d854954941", "davejamesmiller/laravel-breadcrumbs": "5.*", diff --git a/composer.lock b/composer.lock index 8912d13e11..624a0fc83c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,113 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a15b5b4991745824880345223530fa9e", + "content-hash": "f8b29d89e3421b9a03cbaf4c1ae3fba4", "packages": [ + { + "name": "adldap2/adldap2", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/Adldap2/Adldap2.git", + "reference": "54722408c68f12942fcbf4a1b666d90a178ddc5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/54722408c68f12942fcbf4a1b666d90a178ddc5c", + "reference": "54722408c68f12942fcbf4a1b666d90a178ddc5c", + "shasum": "" + }, + "require": { + "ext-ldap": "*", + "illuminate/support": "~5.0", + "php": ">=5.5.9" + }, + "require-dev": { + "mockery/mockery": "~0.9|~1.0", + "phpunit/phpunit": "~4.8|~5.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Adldap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steve Bauman", + "email": "steven_bauman@outlook.com", + "role": "Developer" + } + ], + "description": "A PHP LDAP Package for humans.", + "keywords": [ + "active directory", + "ad", + "adLDAP", + "adldap2", + "directory", + "ldap", + "windows" + ], + "time": "2018-04-19T15:06:54+00:00" + }, + { + "name": "adldap2/adldap2-laravel", + "version": "v4.0.10", + "source": { + "type": "git", + "url": "https://github.com/Adldap2/Adldap2-Laravel.git", + "reference": "a5196cce3b5394d7b5e84eb68b38260cb9f0fd3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Adldap2/Adldap2-Laravel/zipball/a5196cce3b5394d7b5e84eb68b38260cb9f0fd3c", + "reference": "a5196cce3b5394d7b5e84eb68b38260cb9f0fd3c", + "shasum": "" + }, + "require": { + "adldap2/adldap2": "^8.0", + "php": ">=7.0" + }, + "require-dev": { + "mockery/mockery": "~1.0", + "orchestra/testbench": "~3.2", + "phpunit/phpunit": "~6.0" + }, + "type": "project", + "extra": { + "laravel": { + "providers": [ + "Adldap\\Laravel\\AdldapServiceProvider", + "Adldap\\Laravel\\AdldapAuthServiceProvider" + ], + "aliases": { + "Adldap": "Adldap\\Laravel\\Facades\\Adldap" + } + } + }, + "autoload": { + "psr-4": { + "Adldap\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "LDAP Authentication & Management for Laravel.", + "keywords": [ + "adLDAP", + "adldap2", + "laravel", + "ldap" + ], + "time": "2018-09-18T14:27:05+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "1.0.3", @@ -114,7 +219,7 @@ "payment", "sepa" ], - "time": "2018-09-01T12:54:04+00:00" + "time": "2018-10-05T13:50:22+00:00" }, { "name": "davejamesmiller/laravel-breadcrumbs", @@ -1022,16 +1127,16 @@ }, { "name": "laravel/framework", - "version": "v5.7.8", + "version": "v5.7.9", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "763b64a43ebb6042e463aab4214d4cc9722147be" + "reference": "172f69f86bb86e107fb9fafff293b4b01291cf05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/763b64a43ebb6042e463aab4214d4cc9722147be", - "reference": "763b64a43ebb6042e463aab4214d4cc9722147be", + "url": "https://api.github.com/repos/laravel/framework/zipball/172f69f86bb86e107fb9fafff293b4b01291cf05", + "reference": "172f69f86bb86e107fb9fafff293b4b01291cf05", "shasum": "" }, "require": { @@ -1160,7 +1265,7 @@ "framework", "laravel" ], - "time": "2018-10-04T14:47:20+00:00" + "time": "2018-10-09T13:28:28+00:00" }, { "name": "laravel/passport", @@ -5660,7 +5765,7 @@ "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "magento/magento1ce": "<1.9.3.9", "magento/magento1ee": ">=1.9,<1.14.3.2", - "magento/product-community-edition": ">=2,<2.2.5", + "magento/product-community-edition": ">=2,<2.2.6", "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", @@ -5775,7 +5880,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-10-05T18:14:02+00:00" + "time": "2018-10-12T22:19:58+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6760,7 +6865,9 @@ "ext-gd": "*", "ext-intl": "*", "ext-xml": "*", - "ext-zip": "*" + "ext-zip": "*", + "ext-json": "*", + "ext-ldap": "*" }, "platform-dev": [] } diff --git a/config/adldap.php b/config/adldap.php new file mode 100644 index 0000000000..4b879bcc0c --- /dev/null +++ b/config/adldap.php @@ -0,0 +1,259 @@ + [ + + 'default' => [ + + /* + |-------------------------------------------------------------------------- + | Auto Connect + |-------------------------------------------------------------------------- + | + | If auto connect is true, Adldap will try to automatically connect to + | your LDAP server in your configuration. This allows you to assume + | connectivity rather than having to connect manually + | in your application. + | + | If this is set to false, you **must** connect manually before running + | LDAP operations. + | + */ + + 'auto_connect' => env('ADLDAP_AUTO_CONNECT', true), + + /* + |-------------------------------------------------------------------------- + | Connection + |-------------------------------------------------------------------------- + | + | The connection class to use to run raw LDAP operations on. + | + | Custom connection classes must implement: + | + | Adldap\Connections\ConnectionInterface + | + */ + + 'connection' => Adldap\Connections\Ldap::class, + + /* + |-------------------------------------------------------------------------- + | Schema + |-------------------------------------------------------------------------- + | + | The schema class to use for retrieving attributes and generating models. + | + | You can also set this option to `null` to use the default schema class. + | + | For OpenLDAP, you must use the schema: + | + | Adldap\Schemas\OpenLDAP::class + | + | For FreeIPA, you must use the schema: + | + | Adldap\Schemas\FreeIPA::class + | + | Custom schema classes must implement Adldap\Schemas\SchemaInterface + | + */ + + 'schema' => $schema, + + /* + |-------------------------------------------------------------------------- + | Connection Settings + |-------------------------------------------------------------------------- + | + | This connection settings array is directly passed into the Adldap constructor. + | + | Feel free to add or remove settings you don't need. + | + */ + + 'connection_settings' => [ + + /* + |-------------------------------------------------------------------------- + | Account Prefix + |-------------------------------------------------------------------------- + | + | The account prefix option is the prefix of your user accounts in LDAP directory. + | + | This string is prepended to authenticating users usernames. + | + */ + + 'account_prefix' => env('ADLDAP_ACCOUNT_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Account Suffix + |-------------------------------------------------------------------------- + | + | The account suffix option is the suffix of your user accounts in your LDAP directory. + | + | This string is appended to authenticating users usernames. + | + */ + + 'account_suffix' => env('ADLDAP_ACCOUNT_SUFFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Domain Controllers + |-------------------------------------------------------------------------- + | + | The domain controllers option is an array of servers located on your + | network that serve Active Directory. You can insert as many servers or + | as little as you'd like depending on your forest (with the + | minimum of one of course). + | + | These can be IP addresses of your server(s), or the host name. + | + */ + + 'domain_controllers' => explode(' ', env('ADLDAP_CONTROLLERS', '127.0.0.1')), + + /* + |-------------------------------------------------------------------------- + | Port + |-------------------------------------------------------------------------- + | + | The port option is used for authenticating and binding to your LDAP server. + | + */ + + 'port' => env('ADLDAP_PORT', 389), + + /* + |-------------------------------------------------------------------------- + | Timeout + |-------------------------------------------------------------------------- + | + | The timeout option allows you to configure the amount of time in + | seconds that your application waits until a response + | is received from your LDAP server. + | + */ + + 'timeout' => env('ADLDAP_TIMEOUT', 5), + + /* + |-------------------------------------------------------------------------- + | Base Distinguished Name + |-------------------------------------------------------------------------- + | + | The base distinguished name is the base distinguished name you'd + | like to perform query operations on. An example base DN would be: + | + | dc=corp,dc=acme,dc=org + | + | A correct base DN is required for any query results to be returned. + | + */ + + 'base_dn' => env('ADLDAP_BASEDN', 'dc=temp'), + + /* + |-------------------------------------------------------------------------- + | Administrator Account Suffix / Prefix + |-------------------------------------------------------------------------- + | + | This option allows you to set a different account prefix and suffix + | for your configured administrator account upon binding. + | + | If left empty or set to `null`, your `account_prefix` and + | `account_suffix` options above will be used. + | + */ + + 'admin_account_prefix' => env('ADLDAP_ADMIN_ACCOUNT_PREFIX', ''), + 'admin_account_suffix' => env('ADLDAP_ADMIN_ACCOUNT_SUFFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Administrator Username & Password + |-------------------------------------------------------------------------- + | + | When connecting to your LDAP server, a username and password is required + | to be able to query and run operations on your server(s). You can + | use any user account that has these permissions. This account + | does not need to be a domain administrator unless you + | require changing and resetting user passwords. + | + */ + + 'admin_username' => env('ADLDAP_ADMIN_USERNAME', ''), + 'admin_password' => env('ADLDAP_ADMIN_PASSWORD', ''), + + /* + |-------------------------------------------------------------------------- + | Follow Referrals + |-------------------------------------------------------------------------- + | + | The follow referrals option is a boolean to tell active directory + | to follow a referral to another server on your network if the + | server queried knows the information your asking for exists, + | but does not yet contain a copy of it locally. + | + | This option is defaulted to false. + | + */ + + 'follow_referrals' => env('ADLDAP_FOLLOW_REFFERALS', false), + + /* + |-------------------------------------------------------------------------- + | SSL & TLS + |-------------------------------------------------------------------------- + | + | If you need to be able to change user passwords on your server, then an + | SSL or TLS connection is required. All other operations are allowed + | on unsecured protocols. + | + | One of these options are definitely recommended if you + | have the ability to connect to your server securely. + | + */ + + 'use_ssl' => env('ADLDAP_USE_SSL', false), + 'use_tls' => env('ADLDAP_USE_TLS', false), + + ], + + ], + + ], + +]; diff --git a/config/adldap_auth.php b/config/adldap_auth.php new file mode 100644 index 0000000000..ec1ce2674c --- /dev/null +++ b/config/adldap_auth.php @@ -0,0 +1,317 @@ + envNonEmpty('ADLDAP_CONNECTION', 'default'), + + /* + |-------------------------------------------------------------------------- + | Provider + |-------------------------------------------------------------------------- + | + | The LDAP authentication provider to use depending + | if you require database synchronization. + | + | For synchronizing LDAP users to your local applications database, use the provider: + | + | Adldap\Laravel\Auth\DatabaseUserProvider::class + | + | Otherwise, if you just require LDAP authentication, use the provider: + | + | Adldap\Laravel\Auth\NoDatabaseUserProvider::class + | + */ + + 'provider' => Adldap\Laravel\Auth\DatabaseUserProvider::class, + //'provider' => Adldap\Laravel\Auth\NoDatabaseUserProvider::class, + /* + |-------------------------------------------------------------------------- + | Rules + |-------------------------------------------------------------------------- + | + | Rules allow you to control user authentication requests depending on scenarios. + | + | You can create your own rules and insert them here. + | + | All rules must extend from the following class: + | + | Adldap\Laravel\Validation\Rules\Rule + | + */ + + 'rules' => [ + + // Denys deleted users from authenticating. + Adldap\Laravel\Validation\Rules\DenyTrashed::class, + + // Allows only manually imported users to authenticate. + // Adldap\Laravel\Validation\Rules\OnlyImported::class, + + ], + + /* + |-------------------------------------------------------------------------- + | Scopes + |-------------------------------------------------------------------------- + | + | Scopes allow you to restrict the LDAP query that locates + | users upon import and authentication. + | + | All scopes must implement the following interface: + | + | Adldap\Laravel\Scopes\ScopeInterface + |[ + + // Only allows users with a user principal name to authenticate. + // Remove this if you're using OpenLDAP. + //Adldap\Laravel\Scopes\UpnScope::class, + + // Only allows users with a uid to authenticate. + // Uncomment if you're using OpenLDAP. + Adldap\Laravel\Scopes\UidScope::class, + + ], + */ + + 'scopes' => $scopes, + + 'usernames' => [ + + /* + |-------------------------------------------------------------------------- + | LDAP + |-------------------------------------------------------------------------- + | + | Discover: + | + | The discover value is the users attribute you would + | like to locate LDAP users by in your directory. + | + | For example, using the default configuration below, if you're + | authenticating users with an email address, your LDAP server + | will be queried for a user with the a `userprincipalname` + | equal to the entered email address. + | + | Authenticate: + | + | The authenticate value is the users attribute you would + | like to use to bind to your LDAP server. + | + | For example, when a user is located by the above 'discover' + | attribute, the users attribute you specify below will + | be used as the username to bind to your LDAP server. + | + */ + + 'ldap' => [ + + 'discover' => envNonEmpty('ADLDAP_DISCOVER_FIELD', 'userprincipalname'), + 'authenticate' => envNonEmpty('ADLDAP_AUTH_FIELD', 'distinguishedname'), + + ], + + /* + |-------------------------------------------------------------------------- + | Eloquent + |-------------------------------------------------------------------------- + | + | The value you enter is the database column name used for locating + | the local database record of the authenticating user. + | + | If you're using a `username` column instead, change this to `username`. + | + | This option is only applicable to the DatabaseUserProvider. + | + */ + + 'eloquent' => 'email', + + /* + |-------------------------------------------------------------------------- + | Windows Authentication Middleware (SSO) + |-------------------------------------------------------------------------- + | + | Discover: + | + | The 'discover' value is the users attribute you would + | like to locate LDAP users by in your directory. + | + | For example, if 'samaccountname' is the value, then your LDAP server is + | queried for a user with the 'samaccountname' equal to the value of + | $_SERVER['AUTH_USER']. + | + | If a user is found, they are imported (if using the DatabaseUserProvider) + | into your local database, then logged in. + | + | Key: + | + | The 'key' value represents the 'key' of the $_SERVER + | array to pull the users account name from. + | + | For example, $_SERVER['AUTH_USER']. + | + */ + + 'windows' => [ + 'discover' => envNonEmpty('WINDOWS_SSO_DISCOVER', 'samaccountname'), + 'key' => envNonEmpty('WINDOWS_SSO_KEY', 'AUTH_USER'), + ], + ], + + 'passwords' => [ + + /* + |-------------------------------------------------------------------------- + | Password Sync + |-------------------------------------------------------------------------- + | + | The password sync option allows you to automatically synchronize users + | LDAP passwords to your local database. These passwords are hashed + | natively by Laravel using the bcrypt() method. + | + | Enabling this option would also allow users to login to their accounts + | using the password last used when an LDAP connection was present. + | + | If this option is disabled, the local database account is applied a + | random 16 character hashed password upon every login, and will + | lose access to this account upon loss of LDAP connectivity. + | + | This option must be true or false and is only applicable + | to the DatabaseUserProvider. + | + */ + + 'sync' => env('ADLDAP_PASSWORD_SYNC', false), + + /* + |-------------------------------------------------------------------------- + | Column + |-------------------------------------------------------------------------- + | + | This is the column of your users database table + | that is used to store passwords. + | + | Set this to `null` if you do not have a password column. + | + | This option is only applicable to the DatabaseUserProvider. + | + */ + + 'column' => 'password', + + ], + + /* + |-------------------------------------------------------------------------- + | Login Fallback + |-------------------------------------------------------------------------- + | + | The login fallback option allows you to login as a user located on the + | local database if active directory authentication fails. + | + | Set this to true if you would like to enable it. + | + | This option must be true or false and is only + | applicable to the DatabaseUserProvider. + | + */ + + 'login_fallback' => env('ADLDAP_LOGIN_FALLBACK', false), + + /* + |-------------------------------------------------------------------------- + | Sync Attributes + |-------------------------------------------------------------------------- + | + | Attributes specified here will be added / replaced on the user model + | upon login, automatically synchronizing and keeping the attributes + | up to date. + | + | The array key represents the users Laravel model key, and + | the value represents the users LDAP attribute. + | + | This option must be an array and is only applicable + | to the DatabaseUserProvider. + | + */ + + 'sync_attributes' => [ + + 'email' => envNonEmpty('ADLDAP_SYNC_FIELD', 'userprincipalname'), + //'name' => 'cn', + + ], + + /* + |-------------------------------------------------------------------------- + | Logging + |-------------------------------------------------------------------------- + | + | User authentication attempts will be logged using Laravel's + | default logger if this setting is enabled. + | + | No credentials are logged, only usernames. + | + | This is usually stored in the '/storage/logs' directory + | in the root of your application. + | + | This option is useful for debugging as well as auditing. + | + | You can freely remove any events you would not like to log below, + | as well as use your own listeners if you would prefer. + | + */ + + 'logging' => [ + 'enabled' => true, + 'events' => [ + + \Adldap\Laravel\Events\Importing::class => \Adldap\Laravel\Listeners\LogImport::class, + \Adldap\Laravel\Events\Synchronized::class => \Adldap\Laravel\Listeners\LogSynchronized::class, + \Adldap\Laravel\Events\Synchronizing::class => \Adldap\Laravel\Listeners\LogSynchronizing::class, + \Adldap\Laravel\Events\Authenticated::class => \Adldap\Laravel\Listeners\LogAuthenticated::class, + \Adldap\Laravel\Events\Authenticating::class => \Adldap\Laravel\Listeners\LogAuthentication::class, + \Adldap\Laravel\Events\AuthenticationFailed::class => \Adldap\Laravel\Listeners\LogAuthenticationFailure::class, + \Adldap\Laravel\Events\AuthenticationRejected::class => \Adldap\Laravel\Listeners\LogAuthenticationRejection::class, + \Adldap\Laravel\Events\AuthenticationSuccessful::class => \Adldap\Laravel\Listeners\LogAuthenticationSuccess::class, + \Adldap\Laravel\Events\DiscoveredWithCredentials::class => \Adldap\Laravel\Listeners\LogDiscovery::class, + \Adldap\Laravel\Events\AuthenticatedWithWindows::class => \Adldap\Laravel\Listeners\LogWindowsAuth::class, + \Adldap\Laravel\Events\AuthenticatedModelTrashed::class => \Adldap\Laravel\Listeners\LogTrashedModel::class, + + ], + ], + +]; diff --git a/config/auth.php b/config/auth.php index fec9f2f764..66afef9de3 100644 --- a/config/auth.php +++ b/config/auth.php @@ -62,7 +62,6 @@ return [ 'driver' => 'session', 'provider' => 'users', ], - 'api' => [ 'driver' => 'passport', 'provider' => 'users', @@ -88,14 +87,9 @@ return [ 'providers' => [ 'users' => [ - 'driver' => 'eloquent', + 'driver' => envNonEmpty('LOGIN_PROVIDER', 'eloquent'),//'adldap', 'model' => FireflyIII\User::class, ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], ], /* diff --git a/config/firefly.php b/config/firefly.php index 3168d62dfe..d8811e0999 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -95,6 +95,7 @@ return [ 'api_version' => '0.8', 'db_version' => 5, 'maxUploadSize' => 15242880, + 'login_provider' => env('LOGIN_PROVIDER', 'eloquent'), 'allowedMimes' => [ /* plain files */ 'text/plain', diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index be826d4688..1c53b1c8b1 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -551,6 +551,8 @@ return [ 'email_changed_logout' => 'Until you verify your email address, you cannot login.', 'login_with_new_email' => 'You can now login with your new email address.', 'login_with_old_email' => 'You can now login with your old email address again.', + 'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".', + 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', // attachments 'nr_of_attachments' => 'One attachment|:count attachments', diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 6b4999f3f9..ee637199ba 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -191,6 +191,7 @@ return [ 'password_confirmation' => 'Password (again)', 'blocked' => 'Is blocked?', 'blocked_code' => 'Reason for block', + 'login_name' => 'Login', // import 'apply_rules' => 'Apply rules', diff --git a/resources/views/auth/login.twig b/resources/views/auth/login.twig index 094375048e..3308e41329 100644 --- a/resources/views/auth/login.twig +++ b/resources/views/auth/login.twig @@ -54,7 +54,11 @@
- + {% if env('LOGIN_PROVIDER', '') == 'eloquent' %} + + {% else %} + + {% endif %}
@@ -75,7 +79,9 @@ {% if allowRegistration %} {{ 'register_new_account'|_ }}
{% endif %} - {{ 'forgot_my_password'|_ }} + {% if allowReset %} + {{ 'forgot_my_password'|_ }} + {% endif %}
{% endblock %}