diff --git a/.editorconfig b/.editorconfig index 4e4e962..dd9a2b5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,6 +13,3 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 - -[*.json] -indent_size = 2 diff --git a/.gitattributes b/.gitattributes index e74ae9a..58d0cad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,7 +6,6 @@ /.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.lando.yml export-ignore /phpunit.xml export-ignore /pint.json export-ignore /tests export-ignore diff --git a/.github/workflows/laravel-pint.yml b/.github/workflows/laravel-pint.yml index adf6336..0ae57f3 100644 --- a/.github/workflows/laravel-pint.yml +++ b/.github/workflows/laravel-pint.yml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Run Laravel Pint uses: aglipanci/laravel-pint-action@latest diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 91bc61c..4bd4d75 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -6,36 +6,33 @@ jobs: strategy: fail-fast: true matrix: - php: [8.2, 8.3, 8.4, 8.5] - laravel: [11.*, 12.*] - stability: [prefer-stable] + php: [8.3, 8.4, 8.5] + laravel: [12.*, 13.*] include: - - laravel: 11.* - testbench: 9.* - laravel: 12.* - testbench: 10.* + testbench: "^10.0" + - laravel: 13.* + testbench: "^11.0" + exclude: + - php: 8.3 + laravel: 13.* - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: P${{ matrix.php }} - L${{ matrix.laravel }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite3, pdo_sqlite, bcmath, soap, intl, fileinfo + extensions: dom, curl, mbstring, pdo, pdo_sqlite, sqlite3, bcmath, fileinfo coverage: none - - name: Setup problem matchers - run: | - echo "::add-matcher::${{ runner.tool_cache }}/php.json" - echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Install dependencies - run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update - composer update --${{ matrix.stability }} --prefer-dist --no-interaction + uses: ramsey/composer-install@v4 + with: + composer-options: "--no-cache --with=orchestra/testbench:${{ matrix.testbench }}" - name: List Installed Dependencies run: composer show -D diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..beebddf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Run all tests +composer test + +# Run a single test file +vendor/bin/phpunit tests/Feature/AppTestersTest.php + +# Run a single test by name +vendor/bin/phpunit --filter testMethodName + +# Lint / fix code style +vendor/bin/pint + +# Full CI (prepare + test) +composer ci +``` + +## Architecture + +This is a **Laravel package** (not an application) providing SSO authentication middleware for Cornell University apps. The namespace is `CornellCustomDev\LaravelStarterKit\CUAuth\`. + +### Core abstraction: `IdentityManager` + +`src/Managers/IdentityManager.php` is the central interface. `CUAuthServiceProvider` binds one concrete implementation as a singleton based on `CU_AUTH_IDENTITY_MANAGER`: + +- **`ShibIdentityManager`** (`apache-shib`) — reads Shibboleth attributes from Apache server variables (`$_SERVER`). For local development, falls back to `REMOTE_USER` env var when `APP_ENV != production`. +- **`SamlIdentityManager`** (`php-saml`) — uses the OneLogin PHP-SAML toolkit for SAML SP flows. + +Both return a `RemoteIdentity` data object (readonly class) that normalizes identity data from either IdP. + +### Authentication flow + +1. **`CUAuth` middleware** guards routes. It checks `IdentityManager::hasRemoteIdentity()` and redirects to `cu-auth.sso-login` if not authenticated. If `allow_local_login = true`, a locally-authenticated Laravel user bypasses the SSO check entirely (`isLoggedInLocally()` in the `ChecksLocalLogin` trait, shared with `LivewireAuth`). +2. If `require_local_user = true`, the middleware fires the `CUAuthenticated` event after SSO auth. The consuming app must listen for this event to log in or create a local Laravel user. +3. **`AppTesters` middleware** can be stacked after `CUAuth` to restrict non-production access to users listed in `APP_TESTERS`. + +### Routes registered by the package + +- `GET /sso/login` → `AuthController::login` (redirects to IdP) +- `GET /sso/logout` → `AuthController::logout` (SLO) +- `GET/POST /sso/acs` → `AuthController::acs` (SAML assertion consumer / Shib return; CSRF-exempt) +- `GET /sso/metadata` → `AuthController::metadata` (SAML SP metadata) + +### `RemoteIdentity` key methods + +- `id()` — NetID or CWID (unique within the IdP) +- `principalName()` — `eduPersonPrincipalName` (e.g., `netid@cornell.edu`); unique across Cornell and Weill IdPs +- `email()` — returns `principalName` if set, else alias mail +- `uniqueUid()` — **deprecated**; use `principalName()` for cross-IdP uniqueness + +### Livewire support + +Setting `REQUIRE_LIVEWIRE_AUTH=true` makes the service provider override Livewire's update route to require `LivewireAuth` middleware, blocking unauthenticated POSTs to `/livewire/update`. + +### Testing + +Tests use Orchestra Testbench. `FeatureTestCase` sets up an in-memory SQLite database and loads Laravel's default migrations. Unit tests extend `UnitTestCase`. The package has no database migrations of its own. diff --git a/README.md b/README.md index 3cc46fa..9142066 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Middleware for authorizing Laravel users. - Apache mod_shib integration - [AppTesters](#apptesters) - Limit access to users in the `APP_TESTERS` environment variable - [Local Login](#local-login) - Allow Laravel users to log in with a local username and password +- [Livewire Auth](#livewire-auth) - Block unauthenticated Livewire update requests ## Use Cases @@ -163,4 +164,16 @@ For testing purposes, the environment variable "ALLOW_LOCAL_LOGIN" can be set to ```dotenv # File: .env ALLOW_LOCAL_LOGIN=true +``` + + +## Livewire Auth +Blocks unauthenticated POST requests to `/livewire/update`, preventing anonymous users from interacting with Livewire components. + +### Usage + +```dotenv +# File: .env +REQUIRE_LIVEWIRE_AUTH=true +``` diff --git a/composer.json b/composer.json index 29c7ec5..27e1c0f 100644 --- a/composer.json +++ b/composer.json @@ -1,35 +1,47 @@ { - "name": "cornell-custom-dev/laravel-cu-auth", - "description": "A Laravel package for authentication and identity management at Cornell University", - "license": "MIT", - "type": "library", - "require": { - "php": "^8.2", - "ext-openssl": "*", - "illuminate/support": "^11.0|^12.0", - "onelogin/php-saml": "^4.3.1" - }, - "require-dev": { - "laravel/pint": "^1.20", - "orchestra/testbench": "^9.0|^10.0", - "phpunit/phpunit": "^10.5|^11.5" - }, - "autoload": { - "psr-4": { - "CornellCustomDev\\LaravelStarterKit\\CUAuth\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "CornellCustomDev\\LaravelStarterKit\\CUAuth\\Tests\\": "tests" - } - }, - "extra": { - "laravel": { - "providers": [ - "CornellCustomDev\\LaravelStarterKit\\CUAuth\\CUAuthServiceProvider" - ] - } - }, - "prefer-stable": true + "name": "cornell-custom-dev/laravel-cu-auth", + "description": "A Laravel package for authentication and identity management at Cornell University", + "license": "MIT", + "type": "library", + "require": { + "php": "^8.3", + "ext-openssl": "*", + "illuminate/support": "^12.0|^13.0", + "onelogin/php-saml": "^4.3.2" + }, + "require-dev": { + "laravel/pint": "^1.20", + "orchestra/testbench": "^10.0|^11.0", + "phpunit/phpunit": "^11.5|^12.5" + }, + "autoload": { + "psr-4": { + "CornellCustomDev\\LaravelStarterKit\\CUAuth\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "CornellCustomDev\\LaravelStarterKit\\CUAuth\\Tests\\": "tests" + } + }, + "scripts": { + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "test": "@php vendor/bin/phpunit -c ./ --color" + }, + "config": { + "sort-packages": true + }, + "extra": { + "laravel": { + "providers": [ + "CornellCustomDev\\LaravelStarterKit\\CUAuth\\CUAuthServiceProvider" + ] + } + }, + "prefer-stable": true } diff --git a/config/cu-auth.php b/config/cu-auth.php index 205d5d4..4063717 100644 --- a/config/cu-auth.php +++ b/config/cu-auth.php @@ -79,4 +79,15 @@ | */ 'allow_local_login' => boolval(env('ALLOW_LOCAL_LOGIN', false)), + + /* + |-------------------------------------------------------------------------- + | Restrict Access for Livewire Updates + |-------------------------------------------------------------------------- + | + | Add global middleware protection against unauthenticated posting to + | /livewire/update. + | + */ + 'require_livewire_auth' => boolval(env('REQUIRE_LIVEWIRE_AUTH', false)), ]; diff --git a/phpunit.xml b/phpunit.xml index e85414b..805fa72 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,11 +1,14 @@ diff --git a/src/CUAuthServiceProvider.php b/src/CUAuthServiceProvider.php index a194c1b..200fd97 100644 --- a/src/CUAuthServiceProvider.php +++ b/src/CUAuthServiceProvider.php @@ -5,6 +5,7 @@ use CornellCustomDev\LaravelStarterKit\CUAuth\Managers\IdentityManager; use CornellCustomDev\LaravelStarterKit\CUAuth\Managers\SamlIdentityManager; use CornellCustomDev\LaravelStarterKit\CUAuth\Managers\ShibIdentityManager; +use CornellCustomDev\LaravelStarterKit\CUAuth\Middleware\LivewireAuth; use Illuminate\Support\ServiceProvider; class CUAuthServiceProvider extends ServiceProvider @@ -51,5 +52,9 @@ public function boot(): void ]); } $this->loadRoutesFrom(__DIR__.'/../routes/cu-auth.php'); + + if (config('cu-auth.require_livewire_auth')) { + LivewireAuth::requireLivewireAuth(); + } } } diff --git a/src/DataObjects/RemoteIdentity.php b/src/DataObjects/RemoteIdentity.php index fd6a6c2..64bbb70 100644 --- a/src/DataObjects/RemoteIdentity.php +++ b/src/DataObjects/RemoteIdentity.php @@ -7,8 +7,10 @@ public function __construct( public string $idp, public string $uid, + public string $principalName = '', public string $displayName = '', public string $email = '', + private string $mail = '', public array $data = [], ) {} @@ -26,12 +28,11 @@ public static function fromData( return new RemoteIdentity( idp: $idp, uid: $uid, + principalName: trim($eduPersonPrincipalName ?? ''), displayName: $displayName ?? $cn ?? trim(($givenName ?? '').' '.($sn ?? '')), - email: $eduPersonPrincipalName - ?? $mail - ?? '', + mail: trim($mail ?? ''), data: $data, ); } @@ -46,6 +47,9 @@ public function id(): string /** * Provides an id that is unique across Cornell IdPs. + * + * @deprecated Use of uniqueUid() should be replaced with use of principalName() for a unique identifier + * across IdPs, and id() for an identifier within the IdP. */ public function uniqueUid(): string { @@ -55,12 +59,29 @@ public function uniqueUid(): string }; } + public function principalName(): string + { + return $this->principalName; + } + + public function primaryEmail(): string + { + return $this->principalName; + } + + public function emailAlias(): string + { + return $this->mail; + } + /** * Returns the primary email (netid@cornell.edu|cwid@med.cornell.edu) if available, otherwise the alias email. + * + * @deprecated Use of email() should be replaced with primaryEmai() or emailAlias(), as appropriate. */ public function email(): string { - return $this->email; + return $this->primaryEmail() ?: $this->emailAlias(); } /** @@ -73,11 +94,13 @@ public function name(): string public function isCornellIdP(): bool { - return str_contains($this->idp, 'cit.cornell.edu'); + return str_contains($this->idp, 'cit.cornell.edu') + || str_contains($this->principalName, '@cornell.edu'); } public function isWeillIdP(): bool { - return str_contains($this->idp, 'weill.cornell.edu'); + return str_contains($this->idp, 'weill.cornell.edu') + || str_contains($this->principalName, '@med.cornell.edu'); } } diff --git a/src/Listeners/AuthorizeUser.php b/src/Listeners/AuthorizeUser.php index a088e49..164df51 100644 --- a/src/Listeners/AuthorizeUser.php +++ b/src/Listeners/AuthorizeUser.php @@ -20,13 +20,13 @@ public function handle(CUAuthenticated $event, ?RemoteIdentity $remoteIdentity = // Look for a matching user. $userModel = config('auth.providers.users.model'); - $user = $userModel::firstWhere('email', $remoteIdentity->email()); + $user = $userModel::firstWhere('email', $remoteIdentity->primaryEmail()); if (empty($user)) { // User does not exist, so create them. $user = new $userModel; $user->name = $remoteIdentity->name(); - $user->email = $remoteIdentity->email(); + $user->email = $remoteIdentity->primaryEmail(); $user->password = Str::random(32); $user->save(); Log::info("AuthorizeUser: Created user $user->email with ID $user->id."); diff --git a/src/Managers/IdentityManager.php b/src/Managers/IdentityManager.php index 61aaa6b..146fe88 100644 --- a/src/Managers/IdentityManager.php +++ b/src/Managers/IdentityManager.php @@ -7,6 +7,11 @@ interface IdentityManager { + public function hasRemoteIdentity(): bool; + + /** + * @deprecated Use hasRemoteIdentity() instead. + */ public function hasIdentity(): bool; public function getIdentity(): ?RemoteIdentity; diff --git a/src/Managers/SamlIdentityManager.php b/src/Managers/SamlIdentityManager.php index 621824d..6acbc07 100644 --- a/src/Managers/SamlIdentityManager.php +++ b/src/Managers/SamlIdentityManager.php @@ -57,11 +57,17 @@ class SamlIdentityManager implements IdentityManager 'title' => 'urn:oid:2.5.4.12', ]; - public function hasIdentity(): bool + public function hasRemoteIdentity(): bool { return ! empty($this->getIdentity()); } + /** {@inheritDoc} */ + public function hasIdentity(): bool + { + return $this->hasRemoteIdentity(); + } + public function getIdentity(): ?RemoteIdentity { /** @var RemoteIdentity|null $remoteIdentity */ diff --git a/src/Managers/ShibIdentityManager.php b/src/Managers/ShibIdentityManager.php index 54e5798..71e05ae 100644 --- a/src/Managers/ShibIdentityManager.php +++ b/src/Managers/ShibIdentityManager.php @@ -29,11 +29,17 @@ class ShibIdentityManager implements IdentityManager public const SHIB_LOGOUT_URL = '/Shibboleth.sso/Logout'; - public function hasIdentity(): bool + public function hasRemoteIdentity(): bool { return ! empty($this->getIdentity()); } + /** {@inheritDoc} */ + public function hasIdentity(): bool + { + return $this->hasRemoteIdentity(); + } + public function getIdentity(): ?RemoteIdentity { /** @var RemoteIdentity|null $remoteIdentity */ diff --git a/src/Middleware/AppTesters.php b/src/Middleware/AppTesters.php index 4bc4ad4..d630021 100644 --- a/src/Middleware/AppTesters.php +++ b/src/Middleware/AppTesters.php @@ -37,7 +37,7 @@ public function handle(Request $request, Closure $next): Response $appTestersField = config('cu-auth.app_testers_field'); $tester = auth()->user()->$appTestersField ?? ''; } else { - $tester = $this->identityManager->getIdentity()?->uniqueUid() ?: ''; + $tester = $this->identityManager->getIdentity()?->id() ?: ''; } if ($this->app_testers->contains($tester)) { diff --git a/src/Middleware/CUAuth.php b/src/Middleware/CUAuth.php index be617de..27b612b 100644 --- a/src/Middleware/CUAuth.php +++ b/src/Middleware/CUAuth.php @@ -5,11 +5,14 @@ use Closure; use CornellCustomDev\LaravelStarterKit\CUAuth\Events\CUAuthenticated; use CornellCustomDev\LaravelStarterKit\CUAuth\Managers\IdentityManager; +use CornellCustomDev\LaravelStarterKit\CUAuth\Middleware\Concerns\ChecksLocalLogin; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class CUAuth { + use ChecksLocalLogin; + public function __construct( protected IdentityManager $identityManager ) {} @@ -17,7 +20,7 @@ public function __construct( public function handle(Request $request, Closure $next): Response { // If local login is allowed and someone is authenticated, let them through. - if (config('cu-auth.allow_local_login') && auth()->check()) { + if ($this->isLoggedInLocally()) { return $next($request); } @@ -26,6 +29,7 @@ public function handle(Request $request, Closure $next): Response return redirect()->secure($request->getRequestUri()); } + // SSO routes need to pass through so the identity manager can process the SSO responses. $passThrough = in_array($request->path(), [ route('cu-auth.sso-login'), route('cu-auth.sso-logout'), @@ -36,7 +40,8 @@ public function handle(Request $request, Closure $next): Response return $next($request); } - if (! $this->identityManager->hasIdentity()) { + // If we don't have a remote identity, redirect to the SSO login route. + if (! $this->identityManager->hasRemoteIdentity()) { return redirect()->route('cu-auth.sso-login', ['redirect_url' => $request->fullUrl()]); } diff --git a/src/Middleware/Concerns/ChecksLocalLogin.php b/src/Middleware/Concerns/ChecksLocalLogin.php new file mode 100644 index 0000000..30ea3cf --- /dev/null +++ b/src/Middleware/Concerns/ChecksLocalLogin.php @@ -0,0 +1,11 @@ +check(); + } +} diff --git a/src/Middleware/LivewireAuth.php b/src/Middleware/LivewireAuth.php new file mode 100644 index 0000000..019edb1 --- /dev/null +++ b/src/Middleware/LivewireAuth.php @@ -0,0 +1,47 @@ +middleware(['web', LivewireAuth::class]); + }); + } + } + + /** + * Assure all Livewire updates are from authenticated users. + */ + public function handle(Request $request, Closure $next): Response + { + if ($this->isLoggedInLocally() || $this->identityManager->hasRemoteIdentity()) { + return $next($request); + } + + // This is a /livewire/update without a logged in user, so return forbidden. + if (app()->runningInConsole()) { + return response('Forbidden', Response::HTTP_FORBIDDEN); + } + abort(403); + } +} diff --git a/tests/Feature/LivewireAuthTest.php b/tests/Feature/LivewireAuthTest.php new file mode 100644 index 0000000..fb9203d --- /dev/null +++ b/tests/Feature/LivewireAuthTest.php @@ -0,0 +1,66 @@ +setLaravelSession(app('session.store')); + + return $request; + } + + public function testAllowsRequestWithRemoteIdentity() + { + $identityManager = $this->createStub(ShibIdentityManager::class); + $identityManager->method('hasRemoteIdentity')->willReturn(true); + + $response = (new LivewireAuth($identityManager)) + ->handle($this->getLivewireUpdateRequest(), fn () => response('OK')); + + $this->assertTrue($response->isOk()); + } + + public function testBlocksRequestWithNoIdentity() + { + $identityManager = $this->createStub(ShibIdentityManager::class); + $identityManager->method('hasRemoteIdentity')->willReturn(false); + + $response = (new LivewireAuth($identityManager)) + ->handle($this->getLivewireUpdateRequest(), fn () => response('OK')); + + $this->assertTrue($response->isForbidden()); + } + + public function testAllowsLocallyAuthenticatedUserWhenLocalLoginEnabled() + { + config(['cu-auth.allow_local_login' => true]); + $identityManager = $this->createStub(ShibIdentityManager::class); + $identityManager->method('hasRemoteIdentity')->willReturn(false); + auth()->login($this->getTestUser()); + + $response = (new LivewireAuth($identityManager)) + ->handle($this->getLivewireUpdateRequest(), fn () => response('OK')); + + $this->assertTrue($response->isOk()); + } + + public function testBlocksLocallyAuthenticatedUserWhenLocalLoginDisabled() + { + config(['cu-auth.allow_local_login' => false]); + $identityManager = $this->createStub(ShibIdentityManager::class); + $identityManager->method('hasRemoteIdentity')->willReturn(false); + auth()->login($this->getTestUser()); + + $response = (new LivewireAuth($identityManager)) + ->handle($this->getLivewireUpdateRequest(), fn () => response('OK')); + + $this->assertTrue($response->isForbidden()); + } +} diff --git a/tests/Feature/SamlIdentityManagerTest.php b/tests/Feature/SamlIdentityManagerTest.php index 29f3135..533c4eb 100644 --- a/tests/Feature/SamlIdentityManagerTest.php +++ b/tests/Feature/SamlIdentityManagerTest.php @@ -147,7 +147,7 @@ public function testAuthorizeUser() $remoteIdentity = $identityManager->retrieveIdentity([ 'uid' => ['netid'], 'displayName' => ['Test User'], - 'mail' => ['cwid@med.cornell.edu'], + 'eduPersonPrincipalName' => ['cwid@med.cornell.edu'], ]); $event = new CUAuthenticated('netid@cornell.edu'); $listener = new AuthorizeUser($identityManager); diff --git a/tests/Feature/ShibIdentityManagerTest.php b/tests/Feature/ShibIdentityManagerTest.php index 702cb8f..8b7f0b3 100644 --- a/tests/Feature/ShibIdentityManagerTest.php +++ b/tests/Feature/ShibIdentityManagerTest.php @@ -9,6 +9,7 @@ use CornellCustomDev\LaravelStarterKit\CUAuth\Middleware\CUAuth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Event; +use Orchestra\Testbench\Attributes\DefineRoute; class ShibIdentityManagerTest extends FeatureTestCase { @@ -100,8 +101,8 @@ public function testCanFailAuthorizing() config(['cu-auth.require_local_user' => true]); $this->addCUAuthenticatedListener(authorized: false); $request = $this->getApacheAuthRequest('new-user'); - $identityManager = $this->createMock(ShibIdentityManager::class); - $identityManager->method('hasIdentity')->willReturn(true); + $identityManager = $this->createStub(ShibIdentityManager::class); + $identityManager->method('hasRemoteIdentity')->willReturn(true); $response = (new CUAuth($identityManager)) ->handle($request, fn () => response('OK')); @@ -152,9 +153,7 @@ protected static function usesAuthRoutes($router): void $router->get(ShibIdentityManager::SHIB_LOGIN_URL, fn () => 'ShibUrl'); } - /** - * @define-route usesAuthRoutes - */ + #[DefineRoute('usesAuthRoutes')] public function testRoutesAreProtected() { $this->get(route('test'))->assertOk(); @@ -162,7 +161,7 @@ public function testRoutesAreProtected() $this->followingRedirects()->get(route('test.require-cu-auth'))->assertSee('ShibUrl'); } - /** @define-route usesAuthRoutes */ + #[DefineRoute('usesAuthRoutes')] public function testRouteIsProtectedForRemoteUser() { $this->addCUAuthenticatedListener(); @@ -176,7 +175,7 @@ public function testRouteIsProtectedForRemoteUser() $this->followingRedirects()->get(route('test.require-cu-auth'))->assertOk(); } - /** @define-route usesAuthRoutes */ + #[DefineRoute('usesAuthRoutes')] public function testRouteIsProtectedForProductionRemoteUser() { $this->addCUAuthenticatedListener(); @@ -191,7 +190,7 @@ public function testRouteIsProtectedForProductionRemoteUser() $this->followingRedirects()->get(route('test.require-cu-auth'))->assertOk(); } - /** @define-route usesAuthRoutes */ + #[DefineRoute('usesAuthRoutes')] public function testRouteIsProtectedForLocalUser() { config(['cu-auth.apache_shib_user_variable' => 'REMOTE_USER_TEST']); @@ -213,8 +212,8 @@ public function testRouteIsProtectedWithoutUserLookup() $this->addCUAuthenticatedListener(authorized: false); $request = $this->getApacheAuthRequest('new-user'); - $identityManager = $this->createMock(ShibIdentityManager::class); - $identityManager->method('hasIdentity')->willReturn(true); + $identityManager = $this->createStub(ShibIdentityManager::class); + $identityManager->method('hasRemoteIdentity')->willReturn(true); $response = (new CUAuth($identityManager))->handle($request, fn () => response('OK')); $this->assertTrue($response->isOk()); @@ -287,7 +286,7 @@ public function testAuthorizeUser() 'Shib_Identity_Provider' => 'https://shibidp-test.cit.cornell.edu/idp/shibboleth', 'uid' => 'netid', 'displayName' => 'Test User', - 'mail' => 'netid@cornell.edu', + 'eduPersonPrincipalName' => 'netid@cornell.edu', ]); $event = new CUAuthenticated('netid@cornell.edu'); $listener = new AuthorizeUser($identityManager);