-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CU Auth package #67
Open
woodseowl
wants to merge
40
commits into
main
Choose a base branch
from
cu-auth-install
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
CU Auth package #67
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
7d55d3d
Initial concept for CUAuth (mod_shib) integration
woodseowl 5b76076
Add non-prod login protection
woodseowl 42698e0
Adjust middleware and tests
woodseowl b865c93
Refactor for alternate middleware
woodseowl 19b8dcb
Rename configs for clarity
woodseowl 574ff68
Add AppTesters as a separate middleware; Docs
woodseowl bcb542e
Merge branch 'refs/heads/main' into cu-auth
woodseowl 11afed4
Linting fixes
woodseowl 3975ee4
Fix readme language
woodseowl eb6fcc0
Merge branch 'refs/heads/updates' into cu-auth-install
woodseowl 0ab7c75
Passing tests
woodseowl 7f4fdd3
Linting
woodseowl a3d1324
Allow auth without user, fix local override
woodseowl e29914b
Fix AppTesters; config clean up
woodseowl f9cfa52
Readme improvements
woodseowl d7ae145
Tweak AppTesters
woodseowl ca5dfb5
Complete test coverage
woodseowl 0ca73ee
Use aborts
woodseowl d84a7c4
Handle empty app_testers
woodseowl 1121870
Initial ShibIdentity data object
woodseowl 6e3ef07
Linting
woodseowl 3ffe68d
Structure shib mail + name a bit
woodseowl e810e21
Documentation
woodseowl 10bfe99
Move authentication to a controller
woodseowl 06b9f98
Linting
woodseowl 0a69859
request server default fix
woodseowl 325c81f
Merge branch 'main' into cu-auth-install
woodseowl 40a93fd
README updates
woodseowl 20cb727
Refactor for direct shib URLs
woodseowl 7185e38
Test coverage
woodseowl ede88df
Linting
woodseowl 9e5454c
Add weill conditions
woodseowl 017cd1b
Refactor user lookup
woodseowl cc9d6db
Complete session logout
woodseowl c98c7f2
Fix session context for authcontroller
woodseowl 8d362b8
Fix logout for local testing
woodseowl 1792671
Fix AppTesters case
woodseowl b95670f
Doc updates
woodseowl 52b7b48
Shib/apache docs
woodseowl 65bd7af
ShibIdentity improvements
woodseowl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
return [ | ||
/* | ||
|-------------------------------------------------------------------------- | ||
| ApacheShib Configuration | ||
|-------------------------------------------------------------------------- | ||
| | ||
| ApacheShib retrieves user data from server variables populated by the | ||
| Apache shibboleth module (mod_shib). | ||
| | ||
| The default user variable is "REMOTE_USER", but this may differ depending | ||
| on how mod_shib is configured. | ||
| | ||
| For local development without shibboleth, you can add | ||
| REMOTE_USER=<netid> to your project .env file to log in as that user. | ||
| | ||
| To require a local user be logged in based on the remote user, set | ||
| REQUIRE_LOCAL_USER to true. | ||
| | ||
*/ | ||
'apache_shib_user_variable' => env('APACHE_SHIB_USER_VARIABLE', 'REMOTE_USER'), | ||
'remote_user_override' => env('REMOTE_USER'), | ||
|
||
'require_local_user' => env('REQUIRE_LOCAL_USER', false), | ||
|
||
'shibboleth_login_url' => env('SHIBBOLETH_LOGIN_URL', '/Shibboleth.sso/Login'), | ||
'shibboleth_logout_url' => env('SHIBBOLETH_LOGOUT_URL', '/Shibboleth.sso/Logout'), | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| AppTesters Configuration | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Comma-separated list of users to allow in development environments. | ||
| APP_TESTERS_FIELD is the field on the user model to compare against. | ||
| | ||
*/ | ||
'app_testers' => env('APP_TESTERS', ''), | ||
'app_testers_field' => env('APP_TESTERS_FIELD', 'netid'), | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Allow Local Login | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Allow Laravel password-based login? Typically, this would only be used | ||
| for local or automated testing. | ||
| | ||
*/ | ||
'allow_local_login' => boolval(env('ALLOW_LOCAL_LOGIN', false)), | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
namespace CornellCustomDev\LaravelStarterKit\CUAuth; | ||
|
||
use CornellCustomDev\LaravelStarterKit\StarterKitServiceProvider; | ||
use Illuminate\Support\ServiceProvider; | ||
|
||
class CUAuthServiceProvider extends ServiceProvider | ||
{ | ||
const INSTALL_CONFIG_TAG = 'cu-auth-config'; | ||
|
||
public function register(): void | ||
{ | ||
$this->mergeConfigFrom( | ||
path: __DIR__.'/../../config/cu-auth.php', | ||
key: 'cu-auth', | ||
); | ||
} | ||
|
||
public function boot(): void | ||
{ | ||
if ($this->app->runningInConsole()) { | ||
$this->publishes([ | ||
__DIR__.'/../../config/cu-auth.php' => config_path('cu-auth.php'), | ||
], StarterKitServiceProvider::PACKAGE_NAME.':'.self::INSTALL_CONFIG_TAG); | ||
} | ||
$this->loadRoutesFrom(__DIR__.'/routes.php'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
namespace CornellCustomDev\LaravelStarterKit\CUAuth\DataObjects; | ||
|
||
use Illuminate\Http\Request; | ||
|
||
class ShibIdentity | ||
{ | ||
// Shibboleth fields generally available from either cit or weill IdPs. | ||
public const SHIB_FIELDS = [ | ||
'Shib_Application_ID', // <vhost|applicationId> | ||
'Shib_Authentication_Instant', // YYYY-MM-DDT00:00:00.000Z | ||
'Shib_Identity_Provider', // https://shibidp.cit.cornell.edu/idp/shibboleth|https://login.weill.cornell.edu/idp | ||
'Shib_Session_Expires', // timestamp | ||
'Shib_Session_Inactivity', // timestamp | ||
'displayName', // John Doe | ||
'eduPersonAffiliations', // employee;member;staff | ||
'eduPersonPrincipalName', // [email protected]|[email protected] | ||
'eduPersonScopedAffiliation', // employee@[med.]cornell.edu;member@[med.]cornell.edu;[email protected] | ||
'givenName', // John | ||
'mail', // alias email | ||
'sn', // Doe | ||
'uid', // netid|cwid | ||
]; | ||
|
||
public function __construct( | ||
public readonly string $idp, | ||
public readonly string $uid, | ||
public readonly string $displayName = '', | ||
public readonly string $email = '', | ||
public readonly array $serverVars = [], | ||
) {} | ||
|
||
/** | ||
* Shibboleth server variables will be retrieved from the request if not provided. | ||
*/ | ||
public static function fromServerVars(?array $serverVars = null): self | ||
{ | ||
if (empty($serverVars)) { | ||
$serverVars = app('request')->server(); | ||
} | ||
|
||
return new ShibIdentity( | ||
idp: $serverVars['Shib_Identity_Provider'] ?? '', | ||
uid: $serverVars['uid'] ?? '', | ||
displayName: $serverVars['displayName'] | ||
?? $serverVars['cn'] | ||
?? trim(($serverVars['givenName'] ?? '').' '.($serverVars['sn'] ?? '')), | ||
email: $serverVars['eduPersonPrincipalName'] | ||
?? $serverVars['mail'] ?? '', | ||
serverVars: $serverVars, | ||
); | ||
} | ||
|
||
public static function getRemoteUser(?Request $request = null): ?string | ||
{ | ||
if (empty($request)) { | ||
$request = app('request'); | ||
} | ||
|
||
// If this is a local development environment, allow the local override. | ||
$remote_user_override = self::getRemoteUserOverride(); | ||
|
||
// Apache mod_shib populates the remote user variable if someone is logged in. | ||
return $request->server(config('cu-auth.apache_shib_user_variable')) ?: $remote_user_override; | ||
} | ||
|
||
public static function getRemoteUserOverride(): ?string | ||
{ | ||
// If this is a local development environment, allow the local override. | ||
return app()->isLocal() ? config('cu-auth.remote_user_override') : null; | ||
} | ||
|
||
public function isCornellIdP(): bool | ||
{ | ||
return str_contains($this->idp, 'cit.cornell.edu'); | ||
} | ||
|
||
public function isWeillIdP(): bool | ||
{ | ||
return str_contains($this->idp, 'weill.cornell.edu'); | ||
} | ||
|
||
/** | ||
* Provides a uid that is unique across Cornell IdPs. | ||
*/ | ||
public function uniqueUid(): string | ||
{ | ||
return match (true) { | ||
$this->isCornellIdP() => $this->uid, | ||
$this->isWeillIdP() => $this->uid.'_w', | ||
}; | ||
} | ||
|
||
/** | ||
* Returns the primary email ([email protected]|[email protected]) if available, otherwise the alias email. | ||
*/ | ||
public function email(): string | ||
{ | ||
return $this->email; | ||
} | ||
|
||
/** | ||
* Returns the display name if available, otherwise the common name, fallback is "givenName sn". | ||
*/ | ||
public function name(): string | ||
{ | ||
return $this->displayName; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace CornellCustomDev\LaravelStarterKit\CUAuth\Events; | ||
|
||
use Illuminate\Foundation\Events\Dispatchable; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class CUAuthenticated | ||
{ | ||
use Dispatchable, SerializesModels; | ||
|
||
public function __construct( | ||
public readonly string $remoteUser, | ||
) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
namespace CornellCustomDev\LaravelStarterKit\CUAuth\Http\Controllers; | ||
|
||
use CornellCustomDev\LaravelStarterKit\CUAuth\DataObjects\ShibIdentity; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Routing\Controller as BaseController; | ||
use Illuminate\Support\Facades\Auth; | ||
|
||
class AuthController extends BaseController | ||
{ | ||
public function shibbolethLogin(Request $request) | ||
{ | ||
$redirectUri = $request->query('redirect_uri', '/'); | ||
|
||
if (ShibIdentity::getRemoteUser($request)) { | ||
// Already logged in so redirect to the originally intended URL | ||
return redirect()->to($redirectUri); | ||
} | ||
|
||
// Use the Shibboleth login URL | ||
return redirect(config('cu-auth.shibboleth_login_url').'?target='.urlencode($redirectUri)); | ||
} | ||
|
||
public function shibbolethLogout(Request $request) | ||
{ | ||
Auth::logout(); | ||
$request->session()->invalidate(); | ||
$request->session()->regenerateToken(); | ||
|
||
$returnUrl = $request->query('return', '/'); | ||
|
||
if (ShibIdentity::getRemoteUserOverride()) { | ||
// If using locally configured remote user, there is no Shibboleth logout | ||
return redirect()->to($returnUrl); | ||
} | ||
|
||
// Use the Shibboleth logout URL | ||
return redirect(config('cu-auth.shibboleth_logout_url').'?return='.urlencode($returnUrl)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
namespace CornellCustomDev\LaravelStarterKit\CUAuth\Listeners; | ||
|
||
use CornellCustomDev\LaravelStarterKit\CUAuth\DataObjects\ShibIdentity; | ||
use CornellCustomDev\LaravelStarterKit\CUAuth\Events\CUAuthenticated; | ||
use Illuminate\Support\Facades\Log; | ||
use Illuminate\Support\Str; | ||
|
||
class AuthorizeUser | ||
{ | ||
public function handle(CUAuthenticated $event, ?array $serverVars = null): void | ||
{ | ||
$shibboleth = ShibIdentity::fromServerVars($serverVars); | ||
|
||
// Look for a matching user. | ||
$userModel = config('auth.providers.users.model'); | ||
$user = $userModel::firstWhere('email', $shibboleth->email()); | ||
|
||
if (empty($user)) { | ||
// User does not exist, so create them. | ||
$user = new $userModel; | ||
$user->name = $shibboleth->name(); | ||
$user->email = $shibboleth->email(); | ||
$user->password = Str::random(32); | ||
$user->save(); | ||
Log::info("AuthorizeUser: Created user $user->email with ID $user->id."); | ||
} | ||
|
||
auth()->login($user); | ||
Log::info("AuthorizeUser: Logged in user $user->email."); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
namespace CornellCustomDev\LaravelStarterKit\CUAuth\Middleware; | ||
|
||
use Closure; | ||
use CornellCustomDev\LaravelStarterKit\CUAuth\DataObjects\ShibIdentity; | ||
use CornellCustomDev\LaravelStarterKit\CUAuth\Events\CUAuthenticated; | ||
use Illuminate\Http\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
class ApacheShib | ||
{ | ||
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()) { | ||
return $next($request); | ||
} | ||
|
||
// Shibboleth login route is allowed to pass through. | ||
if ($request->path() == route('cu-auth.shibboleth-login')) { | ||
return $next($request); | ||
} | ||
|
||
// remoteUser will be set for authenticated users. | ||
$remoteUser = ShibIdentity::getRemoteUser($request); | ||
|
||
// Unauthenticated get redirected to Shibboleth login. | ||
if (empty($remoteUser)) { | ||
return redirect()->route('cu-auth.shibboleth-login', [ | ||
'redirect_uri' => $request->fullUrl(), | ||
]); | ||
} | ||
|
||
// If requiring a local user, attempt to log in the user. | ||
if (config('cu-auth.require_local_user') && ! auth()->check()) { | ||
event(new CUAuthenticated($remoteUser)); | ||
|
||
// If the authenticated user is still not logged in, return a 403. | ||
if (! auth()->check()) { | ||
if (app()->runningInConsole()) { | ||
return response('Forbidden', Response::HTTP_FORBIDDEN); | ||
} | ||
abort(403); | ||
} | ||
} | ||
|
||
return $next($request); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to use eduPersonPrincipalName (netid email) here instead of mail attribute which is alias email. This can be changed later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently I was already in agreement with you! Looking at the ShibIdentity implementation, it uses eduPersonPrincipalName as the value for email if it is available, and it uses mail as a fallback. (See ShibIdentity::fromServerVars() and the phpdoc for ShibIdentity::email())
So, basically, yes, and already done!