refactor(linkedin): drive OAuth scopes from LINKEDIN_SCOPES env via config
Replace the additive LINKEDIN_EXTRA_SCOPES approach with a single
full-override env var per flow, exploded into an array at the config
layer (config/trypost.php -> platforms.linkedin{,-page}.scopes). This
keeps env values as plain comma-separated strings, lets self-hosters
override the entire set in one place, and removes the controller-side
scope-merge logic.
- config/trypost.php: explode LINKEDIN_SCOPES / LINKEDIN_PAGE_SCOPES
into the scopes arrays (deprecated r_basicprofile stays out of the
personal default)
- LinkedInController: drop resolveScopes(), read config scopes directly
- LinkedInPageController: drop the hardcoded $scopes property, read
config scopes at both call sites
- tests: drive the connect scope assertions from config overrides
- .env.example, docker/.env.docker.example: document LINKEDIN_SCOPES
and LINKEDIN_PAGE_SCOPES
This commit is contained in:
parent
8e164bda36
commit
a2a7f44c9a
6 changed files with 36 additions and 69 deletions
11
.env.example
11
.env.example
|
|
@ -103,11 +103,12 @@ LINKEDIN_CLIENT_ID=
|
|||
LINKEDIN_CLIENT_SECRET=
|
||||
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
|
||||
LINKEDIN_PAGE_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin-page/callback"
|
||||
# Optional: comma-separated extra OAuth scopes appended to the personal
|
||||
# LinkedIn connect flow. Use this if your LinkedIn dev app has legacy or
|
||||
# enterprise products approved — e.g. `r_basicprofile` re-enables
|
||||
# vanityName lookup via /v2/me. Leave empty for the safe default set.
|
||||
# LINKEDIN_EXTRA_SCOPES=r_basicprofile
|
||||
# Optional: comma-separated OAuth scopes for each LinkedIn connect flow.
|
||||
# Override these only if your LinkedIn dev app has legacy/enterprise products
|
||||
# approved — e.g. add `r_basicprofile` to the personal set to re-enable the
|
||||
# vanityName lookup via /v2/me. Leave unset to use the safe defaults.
|
||||
# LINKEDIN_SCOPES="openid,profile,email,w_member_social"
|
||||
# LINKEDIN_PAGE_SCOPES="openid,profile,email,w_organization_social,r_organization_social,rw_organization_admin,w_member_social"
|
||||
|
||||
# X / Twitter (https://developer.twitter.com)
|
||||
X_CLIENT_ID=
|
||||
|
|
|
|||
|
|
@ -34,33 +34,7 @@ public function connect(Request $request): Response|RedirectResponse
|
|||
|
||||
$this->authorize('manageAccounts', $workspace);
|
||||
|
||||
return $this->redirectToProvider($request, $this->driver, $this->resolveScopes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the default LinkedIn scopes with any extra scopes the operator
|
||||
* configured via `LINKEDIN_EXTRA_SCOPES` — comma-separated, e.g.
|
||||
* `r_basicprofile`. Useful when the connected LinkedIn dev app has legacy
|
||||
* or enterprise products not covered by the default Sign-In +
|
||||
* Share-on-LinkedIn pair. Both live under
|
||||
* `config/trypost.php` → `platforms.linkedin`.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
protected function resolveScopes(): array
|
||||
{
|
||||
/** @var array<int, string> $scopes */
|
||||
$scopes = config('trypost.platforms.linkedin.scopes', []);
|
||||
|
||||
$extra = (string) config('trypost.platforms.linkedin.extra_scopes', '');
|
||||
|
||||
if ($extra === '') {
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
$extraScopes = array_filter(array_map('trim', explode(',', $extra)));
|
||||
|
||||
return array_values(array_unique([...$scopes, ...$extraScopes]));
|
||||
return $this->redirectToProvider($request, $this->driver, config('trypost.platforms.linkedin.scopes'));
|
||||
}
|
||||
|
||||
public function callback(Request $request): View
|
||||
|
|
|
|||
|
|
@ -24,16 +24,6 @@ class LinkedInPageController extends SocialController
|
|||
|
||||
protected SocialPlatform $platform = SocialPlatform::LinkedInPage;
|
||||
|
||||
protected array $scopes = [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'w_organization_social',
|
||||
'r_organization_social',
|
||||
'rw_organization_admin',
|
||||
'w_member_social',
|
||||
];
|
||||
|
||||
public function connect(Request $request): SymfonyResponse|RedirectResponse
|
||||
{
|
||||
$this->ensurePlatformEnabled();
|
||||
|
|
@ -55,7 +45,7 @@ public function connect(Request $request): SymfonyResponse|RedirectResponse
|
|||
|
||||
return Inertia::location(
|
||||
Socialite::driver($this->driver)
|
||||
->scopes($this->scopes)
|
||||
->scopes(config('trypost.platforms.linkedin-page.scopes'))
|
||||
->with([
|
||||
'redirect_uri' => config('services.linkedin-openid.redirect_page'),
|
||||
])
|
||||
|
|
@ -80,7 +70,7 @@ public function callback(Request $request): View|RedirectResponse
|
|||
|
||||
try {
|
||||
$socialUser = Socialite::driver($this->driver)
|
||||
->scopes($this->scopes)
|
||||
->scopes(config('trypost.platforms.linkedin-page.scopes'))
|
||||
->with([
|
||||
'redirect_uri' => config('services.linkedin-openid.redirect_page'),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -80,22 +80,23 @@
|
|||
'api' => env('LINKEDIN_API', 'https://api.linkedin.com'),
|
||||
// OAuth host is different from the data API (api.linkedin.com).
|
||||
'oauth_api' => env('LINKEDIN_OAUTH_API', 'https://www.linkedin.com'),
|
||||
// Default OAuth scopes requested on /connect/linkedin. Covers the
|
||||
// two products auto-approved for new apps: Sign In with LinkedIn
|
||||
// (openid, profile, email) + Share on LinkedIn (w_member_social).
|
||||
// `r_basicprofile` is intentionally omitted — it's a 2018-deprecated
|
||||
// legacy scope that new apps can't grant, and requesting it makes
|
||||
// LinkedIn reject the whole authorize request.
|
||||
'scopes' => ['openid', 'profile', 'email', 'w_member_social'],
|
||||
// Comma-separated extra scopes merged onto the defaults above, for
|
||||
// apps with legacy/enterprise products approved — e.g.
|
||||
// `LINKEDIN_EXTRA_SCOPES=r_basicprofile` to re-enable the vanityName
|
||||
// lookup via /v2/me.
|
||||
'extra_scopes' => env('LINKEDIN_EXTRA_SCOPES'),
|
||||
// Comma-separated OAuth scopes requested on /connect/linkedin.
|
||||
// Default covers the two products auto-approved for new apps: Sign
|
||||
// In with LinkedIn (openid, profile, email) + Share on LinkedIn
|
||||
// (w_member_social). The 2018-deprecated `r_basicprofile` is
|
||||
// intentionally absent — new apps can't grant it, and requesting it
|
||||
// makes LinkedIn reject the whole authorize request. Override via
|
||||
// LINKEDIN_SCOPES if your app has legacy/enterprise products
|
||||
// approved (e.g. add `r_basicprofile` to re-enable the vanityName
|
||||
// lookup via /v2/me).
|
||||
'scopes' => array_values(array_filter(array_map('trim', explode(',', (string) env('LINKEDIN_SCOPES', 'openid,profile,email,w_member_social'))))),
|
||||
],
|
||||
'linkedin-page' => [
|
||||
'enabled' => env('LINKEDIN_PAGE_ENABLED', true),
|
||||
'api' => env('LINKEDIN_PAGE_API', 'https://api.linkedin.com'),
|
||||
// Comma-separated OAuth scopes requested on the LinkedIn Company
|
||||
// Page connect flow. Override via LINKEDIN_PAGE_SCOPES.
|
||||
'scopes' => array_values(array_filter(array_map('trim', explode(',', (string) env('LINKEDIN_PAGE_SCOPES', 'openid,profile,email,w_organization_social,r_organization_social,rw_organization_admin,w_member_social'))))),
|
||||
],
|
||||
'x' => [
|
||||
'enabled' => env('X_ENABLED', true),
|
||||
|
|
|
|||
|
|
@ -90,11 +90,12 @@ LINKEDIN_CLIENT_ID=
|
|||
LINKEDIN_CLIENT_SECRET=
|
||||
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
|
||||
LINKEDIN_PAGE_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin-page/callback"
|
||||
# Optional: comma-separated extra OAuth scopes appended to the personal
|
||||
# LinkedIn connect flow. Use this if your LinkedIn dev app has legacy or
|
||||
# enterprise products approved — e.g. `r_basicprofile` re-enables
|
||||
# vanityName lookup via /v2/me. Leave empty for the safe default set.
|
||||
# LINKEDIN_EXTRA_SCOPES=r_basicprofile
|
||||
# Optional: comma-separated OAuth scopes for each LinkedIn connect flow.
|
||||
# Override these only if your LinkedIn dev app has legacy/enterprise products
|
||||
# approved — e.g. add `r_basicprofile` to the personal set to re-enable the
|
||||
# vanityName lookup via /v2/me. Leave unset to use the safe defaults.
|
||||
# LINKEDIN_SCOPES="openid,profile,email,w_member_social"
|
||||
# LINKEDIN_PAGE_SCOPES="openid,profile,email,w_organization_social,r_organization_social,rw_organization_admin,w_member_social"
|
||||
|
||||
X_CLIENT_ID=
|
||||
X_CLIENT_SECRET=
|
||||
|
|
|
|||
|
|
@ -71,22 +71,22 @@
|
|||
return $captured;
|
||||
};
|
||||
|
||||
test('linkedin connect requests the default scope set when LINKEDIN_EXTRA_SCOPES is unset', function () use ($captureConnectScopes) {
|
||||
config(['trypost.platforms.linkedin.extra_scopes' => null]);
|
||||
test('linkedin connect requests the default scope set', function () use ($captureConnectScopes) {
|
||||
config(['trypost.platforms.linkedin.scopes' => ['openid', 'profile', 'email', 'w_member_social']]);
|
||||
|
||||
expect($captureConnectScopes($this))->toEqualCanonicalizing([
|
||||
'openid', 'profile', 'email', 'w_member_social',
|
||||
]);
|
||||
});
|
||||
|
||||
test('linkedin connect appends LINKEDIN_EXTRA_SCOPES to the default scope set', function () use ($captureConnectScopes) {
|
||||
// Backward-compatibility: ops who have legacy products approved on
|
||||
// their LinkedIn app (e.g. r_basicprofile) opt back in via env.
|
||||
config(['trypost.platforms.linkedin.extra_scopes' => 'r_basicprofile, r_emailaddress']);
|
||||
test('linkedin connect requests the scopes configured via LINKEDIN_SCOPES', function () use ($captureConnectScopes) {
|
||||
// Operators whose LinkedIn app has legacy/enterprise products approved
|
||||
// override the default set via LINKEDIN_SCOPES (exploded into the config
|
||||
// array), e.g. re-adding r_basicprofile to restore the vanityName lookup.
|
||||
config(['trypost.platforms.linkedin.scopes' => ['openid', 'profile', 'email', 'w_member_social', 'r_basicprofile']]);
|
||||
|
||||
expect($captureConnectScopes($this))->toEqualCanonicalizing([
|
||||
'openid', 'profile', 'email', 'w_member_social',
|
||||
'r_basicprofile', 'r_emailaddress',
|
||||
'openid', 'profile', 'email', 'w_member_social', 'r_basicprofile',
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue