Преглед изворни кода

Merge branch 'feature/openid-support' of https://github.com/indykoning/2FAuth into indykoning-feature/openid-support

Bubka пре 1 година
родитељ
комит
a407f4742e

+ 8 - 0
.env.example

@@ -221,6 +221,14 @@ WEBAUTHN_ID=null
 
 WEBAUTHN_USER_VERIFICATION=preferred
 
+### OpenID settings ###
+
+# OPENID_AUTHORIZE_URL=
+# OPENID_TOKEN_URL=
+# OPENID_USERINFO_URL=
+# OPENID_CLIENT_ID=
+# OPENID_CLIENT_SECRET=
+
 
 # Use this setting to declare trusted proxied.
 # Supported:

+ 44 - 0
app/Http/Controllers/Auth/SocialiteController.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Facades\Settings;
+use App\Http\Controllers\Controller;
+use App\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Str;
+use Laravel\Socialite\Facades\Socialite;
+
+class SocialiteController extends Controller
+{
+    public function redirect(Request $request, $driver)
+    {
+        return Socialite::driver($driver)->redirect();
+    }
+
+    public function callback(Request $request, $driver)
+    {
+        $socialiteUser = Socialite::driver($driver)->user();
+
+        /** @var User $user */
+        $user = User::firstOrNew([
+            'email' => $socialiteUser->getEmail(),
+        ], [
+            'name' => $socialiteUser->getName(),
+            'password' => bcrypt(Str::random()),
+        ]);
+
+        if (!$user->exists && Settings::get('disableRegistrationSso')) {
+            return response(401);
+        }
+
+        $user->last_seen_at = Carbon::now()->format('Y-m-d H:i:s');
+        $user->save();
+
+        Auth::guard()->login($user, true);
+
+        return redirect('/accounts?authenticated');
+    }
+}

+ 2 - 0
app/Http/Controllers/SinglePageController.php

@@ -27,6 +27,7 @@ class SinglePageController extends Controller
         $isTestingApp       = config('2fauth.config.isTestingApp') ? 'true' : 'false';
         $lang               = App::getLocale();
         $locales            = collect(config('2fauth.locales'))->toJson(); /** @phpstan-ignore-line */
+        $openidAuth      = config('services.openid.client_secret') ? true : false;
 
         // if (Auth::user()->preferences)
 
@@ -35,6 +36,7 @@ class SinglePageController extends Controller
             'appConfig'   => collect([
                 'proxyAuth'      => $proxyAuth,
                 'proxyLogoutUrl' => $proxyLogoutUrl,
+                'openidAuth'     => $openidAuth,
                 'subdirectory'   => $subdir,
             ])->toJson(),
             'defaultPreferences' => $defaultPreferences,

+ 5 - 0
app/Providers/EventServiceProvider.php

@@ -10,9 +10,11 @@ use App\Listeners\CleanIconStorage;
 use App\Listeners\DissociateTwofaccountFromGroup;
 use App\Listeners\ReleaseRadar;
 use App\Listeners\ResetUsersPreference;
+use App\Providers\Socialite\RegisterOpenId;
 use Illuminate\Auth\Events\Registered;
 use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
 use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
+use SocialiteProviders\Manager\SocialiteWasCalled;
 
 class EventServiceProvider extends ServiceProvider
 {
@@ -37,6 +39,9 @@ class EventServiceProvider extends ServiceProvider
         ScanForNewReleaseCalled::class => [
             ReleaseRadar::class,
         ],
+        SocialiteWasCalled::class => [
+            RegisterOpenId::class,
+        ],
     ];
 
     /**

+ 90 - 0
app/Providers/Socialite/OpenId.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Providers\Socialite;
+
+use GuzzleHttp\RequestOptions;
+use InvalidArgumentException;
+use SocialiteProviders\Manager\OAuth2\AbstractProvider;
+use SocialiteProviders\Manager\OAuth2\User;
+use SocialiteProviders\Manager\SocialiteWasCalled;
+
+class OpenId extends AbstractProvider
+{
+    public const IDENTIFIER = 'OPENID';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected $scopes = ['openid profile email'];
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function additionalConfigKeys()
+    {
+        return ['token_url', 'authorize_url', 'userinfo_url'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthUrl($state)
+    {
+        return $this->buildAuthUrlFromBase($this->getConfig('authorize_url'), $state);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getTokenUrl()
+    {
+        return $this->getConfig('token_url');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getUserByToken($token)
+    {
+        $response = $this->getHttpClient()->get($this->getConfig('userinfo_url'), [
+            RequestOptions::HEADERS => [
+                'Authorization' => 'Bearer '.$token,
+            ],
+        ]);
+
+        return json_decode((string) $response->getBody(), true);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function refreshToken($refreshToken)
+    {
+        return $this->getHttpClient()->post($this->getTokenUrl(), [
+            RequestOptions::FORM_PARAMS => [
+                'client_id'     => $this->clientId,
+                'client_secret' => $this->clientSecret,
+                'grant_type'    => 'refresh_token',
+                'refresh_token' => $refreshToken,
+            ],
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function mapUserToObject(array $user)
+    {
+        return (new User())->setRaw($user)->map([
+            'email'              => $user['email'] ?? null,
+            'email_verified'     => $user['email_verified'] ?? null,
+            'name'               => $user['name'] ?? null,
+            'given_name'         => $user['given_name'] ?? null,
+            'family_name'        => $user['family_name'] ?? null,
+            'preferred_username' => $user['preferred_username'] ?? null,
+            'nickname'           => $user['nickname'] ?? null,
+            'groups'             => $user['groups'] ?? null,
+            'id'                 => $user['sub'],
+        ]);
+    }
+}

+ 13 - 0
app/Providers/Socialite/RegisterOpenId.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Providers\Socialite;
+
+use SocialiteProviders\Manager\SocialiteWasCalled;
+
+class RegisterOpenId
+{
+    public function __invoke(SocialiteWasCalled $socialiteWasCalled)
+    {
+        $socialiteWasCalled->extendSocialite('openid', OpenId::class);
+    }
+}

+ 2 - 0
composer.json

@@ -30,9 +30,11 @@
         "laragear/webauthn": "^1.2.0",
         "laravel/framework": "^10.10",
         "laravel/passport": "^11.2",
+        "laravel/socialite": "^5.10",
         "laravel/tinker": "^2.8",
         "laravel/ui": "^4.2",
         "paragonie/constant_time_encoding": "^2.6",
+        "socialiteproviders/manager": "^4.4",
         "spatie/eloquent-sortable": "^4.0.1",
         "spomky-labs/otphp": "^11.0"
     },

+ 221 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "88245443209e7afe41d4a37b26f07c65",
+    "content-hash": "8ab04001cb3cb872bf0848236af3fc20",
     "packages": [
         {
             "name": "brick/math",
@@ -2310,6 +2310,76 @@
             },
             "time": "2023-07-14T13:56:28+00:00"
         },
+        {
+            "name": "laravel/socialite",
+            "version": "v5.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/socialite.git",
+                "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/socialite/zipball/f376b6eda9084899e37ac08bafd64a95edf9c6c0",
+                "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^6.0|^7.0",
+                "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0",
+                "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0",
+                "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0",
+                "league/oauth1-client": "^1.10.1",
+                "php": "^7.2|^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.0",
+                "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0",
+                "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^8.0|^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.x-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Laravel\\Socialite\\SocialiteServiceProvider"
+                    ],
+                    "aliases": {
+                        "Socialite": "Laravel\\Socialite\\Facades\\Socialite"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\Socialite\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
+            "homepage": "https://laravel.com",
+            "keywords": [
+                "laravel",
+                "oauth"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/socialite/issues",
+                "source": "https://github.com/laravel/socialite"
+            },
+            "time": "2023-10-30T22:09:58+00:00"
+        },
         {
             "name": "laravel/tinker",
             "version": "v2.8.2",
@@ -3028,6 +3098,82 @@
             ],
             "time": "2023-08-05T12:09:49+00:00"
         },
+        {
+            "name": "league/oauth1-client",
+            "version": "v1.10.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/oauth1-client.git",
+                "reference": "d6365b901b5c287dd41f143033315e2f777e1167"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167",
+                "reference": "d6365b901b5c287dd41f143033315e2f777e1167",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-openssl": "*",
+                "guzzlehttp/guzzle": "^6.0|^7.0",
+                "guzzlehttp/psr7": "^1.7|^2.0",
+                "php": ">=7.1||>=8.0"
+            },
+            "require-dev": {
+                "ext-simplexml": "*",
+                "friendsofphp/php-cs-fixer": "^2.17",
+                "mockery/mockery": "^1.3.3",
+                "phpstan/phpstan": "^0.12.42",
+                "phpunit/phpunit": "^7.5||9.5"
+            },
+            "suggest": {
+                "ext-simplexml": "For decoding XML-based responses."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev",
+                    "dev-develop": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\OAuth1\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ben Corlett",
+                    "email": "bencorlett@me.com",
+                    "homepage": "http://www.webcomm.com.au",
+                    "role": "Developer"
+                }
+            ],
+            "description": "OAuth 1.0 Client Library",
+            "keywords": [
+                "Authentication",
+                "SSO",
+                "authorization",
+                "bitbucket",
+                "identity",
+                "idp",
+                "oauth",
+                "oauth1",
+                "single sign on",
+                "trello",
+                "tumblr",
+                "twitter"
+            ],
+            "support": {
+                "issues": "https://github.com/thephpleague/oauth1-client/issues",
+                "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1"
+            },
+            "time": "2022-04-15T14:02:14+00:00"
+        },
         {
             "name": "league/oauth2-server",
             "version": "8.5.4",
@@ -4930,6 +5076,80 @@
             ],
             "time": "2023-04-15T23:01:58+00:00"
         },
+        {
+            "name": "socialiteproviders/manager",
+            "version": "v4.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/SocialiteProviders/Manager.git",
+                "reference": "df5e45b53d918ec3d689f014d98a6c838b98ed96"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/df5e45b53d918ec3d689f014d98a6c838b98ed96",
+                "reference": "df5e45b53d918ec3d689f014d98a6c838b98ed96",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0",
+                "laravel/socialite": "~5.0",
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.2",
+                "phpunit/phpunit": "^6.0 || ^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "SocialiteProviders\\Manager\\ServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "SocialiteProviders\\Manager\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Andy Wendt",
+                    "email": "andy@awendt.com"
+                },
+                {
+                    "name": "Anton Komarev",
+                    "email": "a.komarev@cybercog.su"
+                },
+                {
+                    "name": "Miguel Piedrafita",
+                    "email": "soy@miguelpiedrafita.com"
+                },
+                {
+                    "name": "atymic",
+                    "email": "atymicq@gmail.com",
+                    "homepage": "https://atymic.dev"
+                }
+            ],
+            "description": "Easily add new or override built-in providers in Laravel Socialite.",
+            "homepage": "https://socialiteproviders.com",
+            "keywords": [
+                "laravel",
+                "manager",
+                "oauth",
+                "providers",
+                "socialite"
+            ],
+            "support": {
+                "issues": "https://github.com/socialiteproviders/manager/issues",
+                "source": "https://github.com/socialiteproviders/manager"
+            },
+            "time": "2023-08-27T23:46:34+00:00"
+        },
         {
             "name": "spatie/eloquent-sortable",
             "version": "4.0.2",

+ 2 - 1
config/2fauth.php

@@ -71,6 +71,7 @@ return [
         'lastRadarScan' => 0,
         'latestRelease' => false,
         'disableRegistration' => false,
+        'disableRegistrationSso' => false,
     ],
 
     /*
@@ -104,4 +105,4 @@ return [
         'getOtpOnRequest' => true,
     ],
 
-];
+];

+ 11 - 0
config/services/openid.php

@@ -0,0 +1,11 @@
+<?php
+
+return [
+    'token_url' => env('OPENID_TOKEN_URL'),
+    'authorize_url' => env('OPENID_AUTHORIZE_URL'),
+    'userinfo_url' => env('OPENID_USERINFO_URL'),
+
+    'client_id' => env('OPENID_CLIENT_ID'),
+    'client_secret' => env('OPENID_CLIENT_SECRET'),
+    'redirect' => '/socialite/callback/openid',
+];

+ 6 - 0
docker/docker-compose.yml

@@ -78,6 +78,12 @@ services:
       # authentication checks. That means your proxy is fully responsible of the authentication process, 2FAuth will
       # trust him as long as headers are presents.
       - AUTHENTICATION_GUARD=web-guard
+      # OpenId settings
+      # - OPENID_AUTHORIZE_URL=
+      # - OPENID_TOKEN_URL=
+      # - OPENID_USERINFO_URL=
+      # - OPENID_CLIENT_ID=
+      # - OPENID_CLIENT_SECRET=
       # Name of the HTTP headers sent by the reverse proxy that identifies the authenticated user at proxy level.
       # Check your proxy documentation to find out how these headers are named (i.e 'REMOTE_USER', 'REMOTE_EMAIL', etc...)
       # (only relevant when AUTHENTICATION_GUARD is set to 'reverse-proxy-guard')

+ 11 - 1
resources/js/views/auth/Login.vue

@@ -115,6 +115,11 @@
                     {{ $t('auth.login_and_password') }}
                 </a>
             </p>
+            <p v-if="appSettings.openidAuth">{{ $t('auth.sign_in_using') }}&nbsp;
+                <a id="lnkSignWithOpenID" class="is-link" href="/socialite/redirect/openid">
+                    OpenID
+                </a>
+            </p>
             <p v-if="appSettings.disableRegistration == false" class="mt-4">
                 {{ $t('auth.forms.dont_have_account_yet') }}&nbsp;
                 <RouterLink id="lnkRegister" :to="{ name: 'register' }" class="is-link">
@@ -143,6 +148,11 @@
                     {{ $t('auth.webauthn.security_device') }}
                 </a>
             </p>
+            <p v-if="appSettings.openidAuth">{{ $t('auth.sign_in_using') }}&nbsp;
+                <a id="lnkSignWithOpenID" class="is-link" href="/socialite/redirect/openid">
+                    OpenID
+                </a>
+            </p>
             <p v-if="appSettings.disableRegistration == false" class="mt-4">
                 {{ $t('auth.forms.dont_have_account_yet') }}&nbsp;
                 <RouterLink id="lnkRegister" :to="{ name: 'register' }" class="is-link">
@@ -153,4 +163,4 @@
     </FormWrapper>
     <!-- footer -->
     <VueFooter/>
-</template>
+</template>

+ 4 - 0
routes/web.php

@@ -5,6 +5,7 @@ use App\Http\Controllers\Auth\LoginController;
 use App\Http\Controllers\Auth\PasswordController;
 use App\Http\Controllers\Auth\RegisterController;
 use App\Http\Controllers\Auth\ResetPasswordController;
+use App\Http\Controllers\Auth\SocialiteController;
 use App\Http\Controllers\Auth\UserController;
 use App\Http\Controllers\Auth\WebAuthnDeviceLostController;
 use App\Http\Controllers\Auth\WebAuthnLoginController;
@@ -47,6 +48,9 @@ Route::group(['middleware' => ['rejectIfDemoMode', 'throttle:10,1']], function (
 Route::group(['middleware' => ['guest', 'throttle:10,1']], function () {
     Route::post('user/login', [LoginController::class, 'login'])->name('user.login');
     Route::post('webauthn/login', [WebAuthnLoginController::class, 'login'])->name('webauthn.login');
+
+    Route::get('/socialite/redirect/{driver}', [SocialiteController::class, 'redirect'])->name('socialite.redirect');
+    Route::get('/socialite/callback/{driver}', [SocialiteController::class, 'callback'])->name('socialite.callback');
 });
 
 /**