]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'feature/saml' of git://github.com/Xiphoseer/BookStack into Xiphoseer...
authorDan Brown <redacted>
Sat, 16 Nov 2019 12:42:45 +0000 (12:42 +0000)
committerDan Brown <redacted>
Sat, 16 Nov 2019 12:42:45 +0000 (12:42 +0000)
1  2 
app/Config/app.php
app/Config/services.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Kernel.php
composer.json
resources/lang/de/errors.php
resources/lang/de_informal/errors.php
resources/lang/en/errors.php
resources/views/auth/login.blade.php
resources/views/settings/roles/form.blade.php
resources/views/users/form.blade.php

diff --combined app/Config/app.php
index 0d06a9b21d7d846a94b49ab8677ced43b3c676cf,23025a6c45e4c819fd5668c77f1c36546edbd7f8..9dae697da54f7fab813c1c75487b42261efc7a55
@@@ -52,14 -52,11 +52,14 @@@ return 
      'locale' => env('APP_LANG', 'en'),
  
      // Locales available
 -    'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'hu', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'],
 +    'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'hu', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'ko', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW', 'tr'],
  
      //  Application Fallback Locale
      'fallback_locale' => 'en',
  
 +    // Faker Locale
 +    'faker_locale' => 'en_GB',
 +
      // Enable right-to-left text control.
      'rtl' => false,
  
      // Encryption cipher
      'cipher' => 'AES-256-CBC',
  
 -    // Logging configuration
 -    // Options: single, daily, syslog, errorlog
 -    'log' => env('APP_LOGGING', 'single'),
 -
      // Application Services Provides
      'providers' => [
  
          Intervention\Image\ImageServiceProvider::class,
          Barryvdh\DomPDF\ServiceProvider::class,
          Barryvdh\Snappy\ServiceProvider::class,
+         Aacotroneo\Saml2\Saml2ServiceProvider::class,
  
 -
          // BookStack replacement service providers (Extends Laravel)
          BookStack\Providers\PaginationServiceProvider::class,
          BookStack\Providers\TranslationServiceProvider::class,
  
          // Laravel
          'App'       => Illuminate\Support\Facades\App::class,
 +        'Arr'       => Illuminate\Support\Arr::class,
          'Artisan'   => Illuminate\Support\Facades\Artisan::class,
          'Auth'      => Illuminate\Support\Facades\Auth::class,
          'Blade'     => Illuminate\Support\Facades\Blade::class,
          'Schema'    => Illuminate\Support\Facades\Schema::class,
          'Session'   => Illuminate\Support\Facades\Session::class,
          'Storage'   => Illuminate\Support\Facades\Storage::class,
 +        'Str'       => Illuminate\Support\Str::class,
          'URL'       => Illuminate\Support\Facades\URL::class,
          'Validator' => Illuminate\Support\Facades\Validator::class,
          'View'      => Illuminate\Support\Facades\View::class,
          'Setting'  => BookStack\Facades\Setting::class,
          'Views'    => BookStack\Facades\Views::class,
          'Images'   => BookStack\Facades\Images::class,
 +        'Permissions' => BookStack\Facades\Permissions::class,
  
      ],
  
diff --combined app/Config/services.php
index 923015f6e62815e66b388a21f6e9d4b0b2282388,b3dc9f08779ff2e6225bdb37e39d159e40d7246d..0f80a9fc15f4f66c57369d71a19096721b2df4f4
@@@ -22,6 -22,23 +22,6 @@@ return 
      // Callback URL for social authentication methods
      'callback_url' => env('APP_URL', false),
  
 -    'mailgun'  => [
 -        'domain' => '',
 -        'secret' => '',
 -    ],
 -
 -    'ses'      => [
 -        'key'    => '',
 -        'secret' => '',
 -        'region' => 'us-east-1',
 -    ],
 -
 -    'stripe'   => [
 -        'model'  => \BookStack\Auth\User::class,
 -        'key'    => '',
 -        'secret' => '',
 -    ],
 -
      'github'   => [
          'client_id'     => env('GITHUB_APP_ID', false),
          'client_secret' => env('GITHUB_APP_SECRET', false),
          'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
          'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'),
          'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
 -        'user_to_groups' => env('LDAP_USER_TO_GROUPS',false),
 +        'user_to_groups' => env('LDAP_USER_TO_GROUPS', false),
          'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
 -        'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false),
 +        'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
          'tls_insecure' => env('LDAP_TLS_INSECURE', false),
+     ],
+     'saml' => [
+         'name' => env('SAML_NAME', 'SSO'),
+         'enabled' => env('SAML2_ENABLED', false),
+         'auto_register' => env('SAML_AUTO_REGISTER', false),
+         'email_attribute' => env('SAML_EMAIL_ATTRIBUTE', 'email'),
+         'display_name_attribute' => explode('|', env('SAML_DISPLAY_NAME_ATTRIBUTE', 'username')),
+         'user_name_attribute' => env('SAML_USER_NAME_ATTRIBUTE', null),
+         'group_attribute' => env('SAML_GROUP_ATTRIBUTE', 'group'),
+         'remove_from_groups' => env('SAML_REMOVE_FROM_GROUPS',false),
+         'user_to_groups' => env('SAML_USER_TO_GROUPS', false),
+         'id_is_user_name' => env('SAML_ID_IS_USER_NAME', true),
      ]
  
  ];
index 2b51ab99765d069470f66252b67bf514ff7f8e5a,0a9537995c332bc1b0991607bfc90b15ed79663b..0cb050a89c2afd0e2425cec76bbef805ba3538f1
@@@ -98,7 -98,6 +98,7 @@@ class LoginController extends Controlle
  
              $user->save();
              $this->userRepo->attachDefaultRole($user);
 +            $this->userRepo->downloadAndAssignUserAvatar($user);
              auth()->login($user);
          }
  
      {
          $socialDrivers = $this->socialAuthService->getActiveDrivers();
          $authMethod = config('auth.method');
+         $samlEnabled = config('services.saml.enabled') == true;
  
          if ($request->has('email')) {
              session()->flashInput([
              ]);
          }
  
-         return view('auth.login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
+         return view('auth.login', [
+           'socialDrivers' => $socialDrivers,
+           'authMethod' => $authMethod,
+           'samlEnabled' => $samlEnabled,
+         ]);
      }
  
      /**
diff --combined app/Http/Kernel.php
index f9752da09d6492430dd2fe2f4c131dcac301aedf,7794f340151c43071e2504900ecdc46b1e3443f5..027e469c805c6e81a7464a9525905035db7ac53f
@@@ -12,7 -12,7 +12,7 @@@ class Kernel extends HttpKerne
       * @var array
       */
      protected $middleware = [
 -        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
 +        \BookStack\Http\Middleware\CheckForMaintenanceMode::class,
          \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
          \BookStack\Http\Middleware\TrimStrings::class,
          \BookStack\Http\Middleware\TrustProxies::class,
              \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
              \Illuminate\Session\Middleware\StartSession::class,
              \Illuminate\View\Middleware\ShareErrorsFromSession::class,
 +            \Illuminate\Routing\Middleware\ThrottleRequests::class,
              \BookStack\Http\Middleware\VerifyCsrfToken::class,
              \Illuminate\Routing\Middleware\SubstituteBindings::class,
 -            \BookStack\Http\Middleware\Localization::class
 +            \BookStack\Http\Middleware\Localization::class,
 +            \BookStack\Http\Middleware\GlobalViewData::class,
          ],
          'api' => [
              'throttle:60,1',
              'bindings',
          ],
+         'saml' => [
+             \BookStack\Http\Middleware\EncryptCookies::class,
+             \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+             \Illuminate\Session\Middleware\StartSession::class,
+         ],
      ];
  
      /**
diff --combined composer.json
index a8b9456a1856c01fb2bf65e14ea32de0f7c55a15,457ce5093710bbcf6dd6550577759ed838951e72..1d952a0c50ac3b560f8a75858917bd414e95e919
@@@ -5,47 -5,47 +5,48 @@@
      "license": "MIT",
      "type": "project",
      "require": {
 -        "php": ">=7.0.5",
 +        "php": "^7.2",
 +        "ext-curl": "*",
 +        "ext-dom": "*",
 +        "ext-gd": "*",
          "ext-json": "*",
 +        "ext-mbstring": "*",
          "ext-tidy": "*",
 -        "ext-dom": "*",
          "ext-xml": "*",
 -        "ext-mbstring": "*",
 -        "ext-gd": "*",
 -        "ext-curl": "*",
 -        "laravel/framework": "~5.5.44",
 -        "fideloper/proxy": "~3.3",
 -        "intervention/image": "^2.4",
 -        "laravel/socialite": "3.0.x-dev",
 +        "barryvdh/laravel-dompdf": "^0.8.5",
 +        "barryvdh/laravel-snappy": "^0.4.5",
 +        "doctrine/dbal": "^2.9",
 +        "fideloper/proxy": "^4.0",
 +        "gathercontent/htmldiff": "^0.2.1",
 +        "intervention/image": "^2.5",
 +        "laravel/framework": "^6.0",
 +        "laravel/socialite": "^4.2",
          "league/flysystem-aws-s3-v3": "^1.0",
 -        "barryvdh/laravel-dompdf": "^0.8.1",
          "predis/predis": "^1.1",
 -        "gathercontent/htmldiff": "^0.2.1",
 -        "barryvdh/laravel-snappy": "^0.4.0",
 -        "socialiteproviders/slack": "^3.0",
 +        "socialiteproviders/discord": "^2.0",
 +        "socialiteproviders/gitlab": "^3.0",
          "socialiteproviders/microsoft-azure": "^3.0",
          "socialiteproviders/okta": "^1.0",
 -        "socialiteproviders/gitlab": "^3.0",
 -        "socialiteproviders/twitch": "^3.0",
 -        "socialiteproviders/discord": "^2.0",
 -        "doctrine/dbal": "^2.5",
 +        "socialiteproviders/slack": "^3.0",
-         "socialiteproviders/twitch": "^5.0"
++        "socialiteproviders/twitch": "^5.0",
+         "aacotroneo/laravel-saml2": "^1.0"
      },
      "require-dev": {
 -        "filp/whoops": "~2.0",
 -        "fzaninotto/faker": "~1.4",
 -        "mockery/mockery": "~1.0",
 -        "phpunit/phpunit": "~6.0",
 -        "symfony/css-selector": "3.1.*",
 -        "symfony/dom-crawler": "3.1.*",
 -        "laravel/browser-kit-testing": "^2.0",
 -        "barryvdh/laravel-ide-helper": "^2.4.1",
 -        "barryvdh/laravel-debugbar": "^3.1.0",
 -        "squizlabs/php_codesniffer": "^3.2"
 +        "barryvdh/laravel-debugbar": "^3.2.8",
 +        "barryvdh/laravel-ide-helper": "^2.6.4",
 +        "facade/ignition": "^1.4",
 +        "fzaninotto/faker": "^1.4",
 +        "laravel/browser-kit-testing": "^5.1",
 +        "mockery/mockery": "^1.0",
 +        "nunomaduro/collision": "^3.0",
 +        "phpunit/phpunit": "^8.0",
 +        "squizlabs/php_codesniffer": "^3.4",
 +        "wnx/laravel-stats": "^2.0"
      },
      "autoload": {
          "classmap": [
 -            "database"
 +            "database/seeds",
 +            "database/factories"
          ],
          "psr-4": {
              "BookStack\\": "app/"
      },
      "scripts": {
          "post-root-package-install": [
 -            "php -r \"file_exists('.env') || copy('.env.example', '.env');\""
 +            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
          ],
          "post-create-project-cmd": [
 -            "php artisan key:generate"
 +            "@php artisan key:generate --ansi"
          ],
          "pre-update-cmd": [
 -            "php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
 -            "php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
 +            "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
 +            "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
          ],
          "pre-install-cmd": [
 -            "php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
 -            "php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
 +            "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
 +            "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
          ],
          "post-install-cmd": [
 -            "php artisan cache:clear",
 -            "php artisan view:clear"
 +            "@php artisan cache:clear",
 +            "@php artisan view:clear"
          ],
          "post-autoload-dump": [
              "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
 -            "@php artisan package:discover"
 +            "@php artisan package:discover --ansi"
          ],
          "refresh-test-database": [
 -            "php artisan migrate:refresh --database=mysql_testing",
 -            "php artisan db:seed --class=DummyContentSeeder --database=mysql_testing"
 +            "@php artisan migrate:refresh --database=mysql_testing",
 +            "@php artisan db:seed --class=DummyContentSeeder --database=mysql_testing"
          ]
      },
      "config": {
          "optimize-autoloader": true,
          "preferred-install": "dist",
 +        "sort-packages": true,
          "platform": {
 -            "php": "7.0.5"
 +            "php": "7.2.0"
 +        }
 +    },
 +    "extra": {
 +        "laravel": {
 +            "dont-discover": []
          }
 -    }
 +    },
 +    "minimum-stability": "dev",
 +    "prefer-stable": true
  }
index ccec60561138cfc526bbe7d2dac846c2305badf6,2cd9d8b30302369ce66fd037b875726e0d2c0580..ff2bf8653d85f59cc3e9e976062fd4295e7127e9
@@@ -1,13 -1,11 +1,13 @@@
  <?php
 +/**
 + * Text shown in error messaging.
 + */
  return [
 -    /**
 -     * Error text strings.
 -     */
 -    // Pages
 +
 +    // Permissions
      'permission' => 'Sie haben keine Berechtigung, auf diese Seite zuzugreifen.',
      'permissionJson' => 'Sie haben keine Berechtigung, die angeforderte Aktion auszuführen.',
 +
      // Auth
      'error_user_exists_different_creds' => 'Ein Benutzer mit der E-Mail-Adresse :email ist bereits mit anderen Anmeldedaten registriert.',
      'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melden Sie sich an.',
@@@ -17,6 -15,8 +17,8 @@@
      'ldap_fail_authed' => 'LDAP-Zugriff mit DN und Passwort ist fehlgeschlagen',
      'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert.',
      'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.',
+     'saml_already_logged_in' => 'Sie sind bereits angemeldet',
+     'saml_user_not_registered' => 'Kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert',
      'social_no_action_defined' => 'Es ist keine Aktion definiert',
      'social_login_bad_response' => "Fehler bei der :socialAccount-Anmeldung: \n:error",
      'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melden Sie sich mit dem :socialAccount-Konto an.',
@@@ -27,8 -27,6 +29,8 @@@
      'social_account_register_instructions' => 'Wenn Sie bisher keinen Social-Media Konto besitzen, können Sie ein solches Konto mit der :socialAccount Option anlegen.',
      'social_driver_not_found' => 'Treiber für Social-Media-Konten nicht gefunden',
      'social_driver_not_configured' => 'Ihr :socialAccount-Konto ist nicht korrekt konfiguriert.',
 +    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 +
      // System
      'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stellen Sie sicher, dass dieser Ordner auf dem Server beschreibbar ist.',
      'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
      // Attachments
      'attachment_page_mismatch' => 'Die Seite stimmte nach dem Hochladen des Anhangs nicht überein.',
      'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
 +
      // Pages
      'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stellen Sie sicher, dass Sie mit dem Internet verbunden sind, bevor Sie den Entwurf dieser Seite speichern.',
      'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
 +
      // Entities
      'entity_not_found' => 'Eintrag nicht gefunden',
 -    'book_not_found' => 'Buch nicht gefunden',
      'bookshelf_not_found' => 'Regal nicht gefunden',
 +    'book_not_found' => 'Buch nicht gefunden',
      'page_not_found' => 'Seite nicht gefunden',
      'chapter_not_found' => 'Kapitel nicht gefunden',
      'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden.',
      'selected_book_chapter_not_found' => 'Das gewählte Buch oder Kapitel wurde nicht gefunden.',
      'guests_cannot_save_drafts' => 'Gäste können keine Entwürfe speichern',
 +
      // Users
      'users_cannot_delete_only_admin' => 'Sie können den einzigen Administrator nicht löschen.',
      'users_cannot_delete_guest' => 'Sie können den Gast-Benutzer nicht löschen',
 +
      // Roles
      'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
      'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
      'comment_add' => 'Beim Hinzufügen des Kommentars ist ein Fehler aufgetreten.',
      'comment_delete' => 'Beim Löschen des Kommentars ist ein Fehler aufgetreten.',
      'empty_comment' => 'Kann keinen leeren Kommentar hinzufügen',
 +
      // Error pages
      '404_page_not_found' => 'Seite nicht gefunden',
      'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Sie angefordert haben, wurde nicht gefunden.',
      'return_home' => 'Zurück zur Startseite',
      'error_occurred' => 'Es ist ein Fehler aufgetreten',
      'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
 -    'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.'
 +    'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.',
 +
  ];
index 9b5b5166b810d40cab20f4f94e40cc0a26b3e7fa,420c35c8d5bd849f3195020d25ed78bc0bbf7c09..e6235015660218d3c65faa587c90246ead863d1a
@@@ -1,85 -1,33 +1,80 @@@
  <?php
 -
 -// Extends 'de'
 +/**
 + * Text shown in error messaging.
 + */
  return [
 -    // Pages
 +
 +    // Permissions
      'permission' => 'Du hast keine Berechtigung, auf diese Seite zuzugreifen.',
      'permissionJson' => 'Du hast keine Berechtigung, die angeforderte Aktion auszuführen.',
  
      // Auth
++    'saml_already_logged_in' => 'Du bist bereits angemeldet',
 +    'error_user_exists_different_creds' => 'Ein Benutzer mit der E-Mail-Adresse :email ist bereits mit anderen Anmeldedaten registriert.',
      'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melde dich an.',
      'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.',
-     'email_confirmation_expired' => 'Der Bestätigungslink ist abgelaufen. Es wurde eine neue Bestätigungs-E-Mail gesendet.',
-     'ldap_fail_anonymous' => 'Anonymer LDAP-Zugriff ist fehlgeschlafgen',
-     'ldap_fail_authed' => 'LDAP-Zugriff mit DN und Passwort ist fehlgeschlagen',
-     'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert.',
-     'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.',
-     'social_no_action_defined' => 'Es ist keine Aktion definiert',
-     'social_login_bad_response' => "Fehler bei der :socialAccount-Anmeldung: \n:error",
 -    'saml_already_logged_in' => 'Du bist bereits angemeldet',
++
      'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.',
      'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
 +    'social_account_existing' => 'Dieses :socialAccount-Konto ist bereits mit Ihrem Profil verknüpft.',
 +    'social_account_already_used_existing' => 'Dieses :socialAccount-Konto wird bereits von einem anderen Benutzer verwendet.',
      'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.',
      'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.',
 +    'social_driver_not_found' => 'Treiber für Social-Media-Konten nicht gefunden',
 +    'social_driver_not_configured' => 'Ihr :socialAccount-Konto ist nicht korrekt konfiguriert.',
 +    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
  
      // System
      'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stelle sicher, dass dieser Ordner auf dem Server beschreibbar ist.',
 +    'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
      'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.',
      'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
 +    'uploaded'  => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.',
 +    'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
 +    'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.',
 +    'file_upload_timeout' => 'Der Upload der Datei ist abgelaufen.',
 +
 +    // Attachments
 +    'attachment_page_mismatch' => 'Die Seite stimmte nach dem Hochladen des Anhangs nicht überein.',
 +    'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
  
      // Pages
      'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.',
      'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
  
 +    // Entities
 +    'entity_not_found' => 'Eintrag nicht gefunden',
 +    'bookshelf_not_found' => 'Regal nicht gefunden',
 +    'book_not_found' => 'Buch nicht gefunden',
 +    'page_not_found' => 'Seite nicht gefunden',
 +    'chapter_not_found' => 'Kapitel nicht gefunden',
 +    'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden.',
 +    'selected_book_chapter_not_found' => 'Das gewählte Buch oder Kapitel wurde nicht gefunden.',
 +    'guests_cannot_save_drafts' => 'Gäste können keine Entwürfe speichern',
 +
      // Users
      'users_cannot_delete_only_admin' => 'Du kannst den einzigen Administrator nicht löschen.',
      'users_cannot_delete_guest' => 'Du kannst den Gast-Benutzer nicht löschen',
  
 +    // Roles
 +    'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
 +    'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
 +    'role_registration_default_cannot_delete' => 'Diese Rolle kann nicht gelöscht werden, solange sie als Standardrolle für neue Registrierungen gesetzt ist',
 +    'role_cannot_remove_only_admin' => 'Dieser Benutzer ist der einzige Benutzer, welchem die Administratorrolle zugeordnet ist. Ordnen Sie die Administratorrolle einem anderen Benutzer zu, bevor Sie versuchen, sie hier zu entfernen.',
 +
 +    // Comments
 +    'comment_list' => 'Beim Abrufen der Kommentare ist ein Fehler aufgetreten.',
 +    'cannot_add_comment_to_draft' => 'Du kannst keine Kommentare zu einem Entwurf hinzufügen.',
 +    'comment_add' => 'Beim Hinzufügen des Kommentars ist ein Fehler aufgetreten.',
 +    'comment_delete' => 'Beim Löschen des Kommentars ist ein Fehler aufgetreten.',
 +    'empty_comment' => 'Kann keinen leeren Kommentar hinzufügen',
 +
      // Error pages
 +    '404_page_not_found' => 'Seite nicht gefunden',
      'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.',
 +    'return_home' => 'Zurück zur Startseite',
 +    'error_occurred' => 'Es ist ein Fehler aufgetreten',
 +    'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
 +    'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.',
 +
  ];
index c3b47744d31f5fc63081ec6d50136511721941c1,40c0bbffbdce7c52ef26ef9a8efac0bd065fc06c..37de5dcc1fa5693aa3229745b13d9916099acf8a
@@@ -17,6 -17,8 +17,8 @@@ return 
      'ldap_fail_authed' => 'LDAP access failed using given dn & password details',
      'ldap_extension_not_installed' => 'LDAP PHP extension not installed',
      'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
+     'saml_already_logged_in' => 'Already logged in',
+     'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
      'social_no_action_defined' => 'No action defined',
      'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
      'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.',
@@@ -27,7 -29,6 +29,7 @@@
      'social_account_register_instructions' => 'If you do not yet have an account, You can register an account using the :socialAccount option.',
      'social_driver_not_found' => 'Social driver not found',
      'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
 +    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
  
      // System
      'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
index fbf540d7105fd6f0969956d7291413471d820b53,8d89c128885d98d47aa83d42a5ec1100f4d422fc..7e4a3992b3510a4f28927c7b46176c041f3263ee
@@@ -7,7 -7,7 +7,7 @@@
          <div class="my-l">&nbsp;</div>
  
          <div class="card content-wrap auto-height">
 -            <h1 class="list-heading">{{ title_case(trans('auth.log_in')) }}</h1>
 +            <h1 class="list-heading">{{ Str::title(trans('auth.log_in')) }}</h1>
  
              <form action="{{ url('/login') }}" method="POST" id="login-form" class="mt-l">
                  {!! csrf_field() !!}
                              'name' => 'remember',
                              'checked' => false,
                              'value' => 'on',
 -                            'tabindex' => 1,
                              'label' => trans('auth.remember_me'),
                          ])
                      </div>
  
                      <div class="text-right">
 -                        <button class="button primary" tabindex="1">{{ title_case(trans('auth.log_in')) }}</button>
 +                        <button class="button">{{ Str::title(trans('auth.log_in')) }}</button>
                      </div>
                  </div>
  
                  @endforeach
              @endif
  
+             @if($samlEnabled)
+               <hr class="my-l">
+               <div>
+                   <a id="saml-login" class="button outline block svg" href="{{ url("/saml2/login") }}">
+                       {{-- @icon('auth/github') --}}
+                       {{ trans('auth.log_in_with', ['socialDriver' => config('services.saml.name')]) }}
+                   </a>
+               </div>
+             @endif
              @if(setting('registration-enabled', false))
                  <div class="text-center pb-s">
                      <hr class="my-l">
@@@ -54,4 -65,4 +64,4 @@@
          </div>
      </div>
  
- @stop
+ @stop
index 20b8d65ed9c22fd12a2050d6e2f2ba3a060d04d5,d7c1fc47ce826dff36ee09b4112d00d9c5c4315f..94e63b158243bc8b2524dd3e838844734dcc79d1
@@@ -19,7 -19,7 +19,7 @@@
                      @include('form.text', ['name' => 'description'])
                  </div>
  
-                 @if(config('auth.method') === 'ldap')
+                 @if(config('auth.method') === 'ldap' || config('services.saml.enabled') === true)
                      <div class="form-group">
                          <label for="name">{{ trans('settings.role_external_auth_id') }}</label>
                          @include('form.text', ['name' => 'external_auth_id'])
@@@ -38,7 -38,6 +38,7 @@@
                  <div>@include('settings.roles.checkbox', ['permission' => 'user-roles-manage', 'label' => trans('settings.role_manage_roles')])</div>
                  <div>@include('settings.roles.checkbox', ['permission' => 'restrictions-manage-all', 'label' => trans('settings.role_manage_entity_permissions')])</div>
                  <div>@include('settings.roles.checkbox', ['permission' => 'restrictions-manage-own', 'label' => trans('settings.role_manage_own_entity_permissions')])</div>
 +                <div>@include('settings.roles.checkbox', ['permission' => 'templates-manage', 'label' => trans('settings.role_manage_page_templates')])</div>
                  <div>@include('settings.roles.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
              </div>
          </div>
          @if (isset($role) && $role->id)
              <a href="{{ url("/settings/roles/delete/{$role->id}") }}" class="button outline">{{ trans('settings.role_delete') }}</a>
          @endif
 -        <button type="submit" class="button primary">{{ trans('settings.role_save') }}</button>
 +        <button type="submit" class="button">{{ trans('settings.role_save') }}</button>
      </div>
  
  </div>
              {{ trans('settings.role_users_none') }}
          </p>
      @endif
- </div>
+ </div>
index 32b717ec8c2ec4db4ef10211f54f0b1aab30f054,7a3d44935ba8ec16e9f2bfa41d4e3b53b2a95df9..6c08cad44f12a85c87f4f9b695c47b710b4cddd7
          <div>
              @if($authMethod !== 'ldap' || userCan('users-manage'))
                  <label for="email">{{ trans('auth.email') }}</label>
 -                @include('form.text', ['name' => 'email'])
 +                @include('form.text', ['name' => 'email', 'disabled' => !userCan('users-manage')])
              @endif
          </div>
      </div>
  </div>
  
- @if($authMethod === 'ldap' && userCan('users-manage'))
+ @if(($authMethod === 'ldap' || config('services.saml.enabled') === true) && userCan('users-manage'))
      <div class="grid half gap-xl v-center">
          <div>
              <label class="setting-list-label">{{ trans('settings.users_external_auth_id') }}</label>
  @endif
  
  @if($authMethod === 'standard')
 -    <div>
 +    <div new-user-password>
          <label class="setting-list-label">{{ trans('settings.users_password') }}</label>
 -        <p class="small">{{ trans('settings.users_password_desc') }}</p>
 -        @if(isset($model))
 +
 +        @if(!isset($model))
              <p class="small">
 -                {{ trans('settings.users_password_warning') }}
 +                {{ trans('settings.users_send_invite_text') }}
              </p>
 +
 +            @include('components.toggle-switch', [
 +                'name' => 'send_invite',
 +                'value' => old('send_invite', 'true') === 'true',
 +                'label' => trans('settings.users_send_invite_option')
 +            ])
 +
          @endif
 -        <div class="grid half mt-m gap-xl">
 -            <div>
 -                <label for="password">{{ trans('auth.password') }}</label>
 -                @include('form.password', ['name' => 'password'])
 -            </div>
 -            <div>
 -                <label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
 -                @include('form.password', ['name' => 'password-confirm'])
 +
 +        <div id="password-input-container" @if(!isset($model)) style="display: none;" @endif>
 +            <p class="small">{{ trans('settings.users_password_desc') }}</p>
 +            @if(isset($model))
 +                <p class="small">
 +                    {{ trans('settings.users_password_warning') }}
 +                </p>
 +            @endif
 +            <div class="grid half mt-m gap-xl">
 +                <div>
 +                    <label for="password">{{ trans('auth.password') }}</label>
 +                    @include('form.password', ['name' => 'password'])
 +                </div>
 +                <div>
 +                    <label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
 +                    @include('form.password', ['name' => 'password-confirm'])
 +                </div>
              </div>
          </div>
 +
      </div>
- @endif
+ @endif