Skip to content
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

[3.3] Openid logout #2224

Open
wants to merge 48 commits into
base: Development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
41371f3
Implement openID connect logout
GregoryPost Jul 24, 2024
2658782
Update authCallback.php
GregoryPost Dec 18, 2024
af8d1ed
Update authCallback.php
GregoryPost Dec 18, 2024
115e32b
Update authCallback.php
GregoryPost Dec 18, 2024
c2d53f9
Update authCallback.php
GregoryPost Dec 18, 2024
ac32f07
Merge branch 'Development' into openid_logout
Atticus29 Dec 18, 2024
504d4b3
Update authCallback.php
GregoryPost Dec 18, 2024
c612101
store third party sid
GregoryPost Jan 8, 2025
88fde09
squashme
GregoryPost Jan 8, 2025
371f69d
squashme2
GregoryPost Jan 8, 2025
c215516
SQAUSH2
GregoryPost Jan 8, 2025
98a9642
test
GregoryPost Jan 8, 2025
282f17c
sQUISH
GregoryPost Jan 8, 2025
6cd4851
Squishme
GregoryPost Jan 9, 2025
46ad1a8
Implement session destruction
GregoryPost Jan 14, 2025
31c2567
try forceLogout
Atticus29 Jan 15, 2025
b7ec63b
add the logout call ya dufus
Atticus29 Jan 15, 2025
edb5dc6
remove translation cruft
Atticus29 Jan 15, 2025
e41219a
add vardump of sesssion
Atticus29 Jan 22, 2025
b46f238
add cookie-based logout
Atticus29 Jan 22, 2025
4e9a974
Update logout.php
GregoryPost Jan 22, 2025
d440fa9
Update logout.php
GregoryPost Jan 23, 2025
841c98b
debug
GregoryPost Jan 23, 2025
8fbb043
prevent third-party authentication workflow from creating an entry in…
Atticus29 Jan 23, 2025
5cdcd98
debug
GregoryPost Jan 23, 2025
76ea906
debug
GregoryPost Jan 23, 2025
e7a0638
debug
GregoryPost Jan 23, 2025
1fabea5
debug
GregoryPost Jan 23, 2025
a658643
debug
GregoryPost Jan 23, 2025
486b00a
add more debugging print statements
Atticus29 Jan 23, 2025
73c4053
do not start a new session if the originalSessionId matches the targe…
Atticus29 Jan 23, 2025
b0feef9
add more print statements
Atticus29 Jan 23, 2025
b00072a
rename parameter to be more accurate
Atticus29 Jan 23, 2025
185e4d5
add session id tracker in search page
Atticus29 Jan 23, 2025
c275534
target the maybe correct currentSessionId
Atticus29 Jan 23, 2025
3373598
add print statement for === PHP_SESSION_ACTIVE
Atticus29 Feb 13, 2025
4047db3
detect remote logout
GregoryPost Feb 13, 2025
62d7da8
Update OpenIdProfileManager.php
GregoryPost Feb 13, 2025
b60ff07
merge me
GregoryPost Feb 14, 2025
18e18b5
Update OpenIdProfileManager.php
GregoryPost Feb 14, 2025
d610db9
remove var_dump and add sql
Atticus29 Feb 14, 2025
9220786
update the docs
Atticus29 Feb 14, 2025
b35bf4e
Merge branch 'Development' into openid_logout
Atticus29 Feb 14, 2025
b1f65b9
remove cruft
Atticus29 Feb 14, 2025
c7f3459
replace variable names with all upper-case
Atticus29 Feb 19, 2025
eb422f6
make and paths relative in config/auth_config_template.php
Atticus29 Feb 19, 2025
ef1911b
Merge branch 'Development' into openid_logout
Atticus29 Feb 19, 2025
9598f36
Ensure that $pHandler exists before resetting it
Atticus29 Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 94 additions & 62 deletions classes/OpenIdProfileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,116 +4,148 @@

include_once('ProfileManager.php');

class OpenIdProfileManager extends ProfileManager{
class OpenIdProfileManager extends ProfileManager
{

public function authenticate($sub='', $provider=''){
public function authenticate($sub = '', $provider = '')
{
$status = false;
unset($_SESSION['userrights']);
unset($_SESSION['userparams']);
$status = $this->authenticateUsingOidSub($sub, $provider);
if($status){
if(strlen($this->displayName) > 15) $this->displayName = $this->userName;
if(strlen($this->displayName) > 15) $this->displayName = substr($this->displayName,0,10).'...';
$this->reset();
$this->setUserRights();
$this->setUserParams();
if($this->rememberMe) $this->setTokenCookie();
if(!isset($GLOBALS['SYMB_UID']) || !$GLOBALS['SYMB_UID']){
$this->resetConnection();
$sql = 'UPDATE users SET lastLoginDate = NOW() WHERE (uid = ?)';
if($stmt = $this->conn->prepare($sql)){
$stmt->bind_param('i', $this->uid);
$stmt->execute();
$stmt->close();
}
}
}
$status = $this->authenticateUsingOidSub($sub, $provider);
if ($status) {
if (strlen($this->displayName) > 15) $this->displayName = $this->userName;
if (strlen($this->displayName) > 15) $this->displayName = substr($this->displayName, 0, 10) . '...';
$this->reset();
$this->setUserRights();
$this->setUserParams();
// if($this->rememberMe) $this->setTokenCookie();
if (!isset($GLOBALS['SYMB_UID']) || !$GLOBALS['SYMB_UID']) {
$this->resetConnection();
$sql = 'UPDATE users SET lastLoginDate = NOW() WHERE (uid = ?)';
if ($stmt = $this->conn->prepare($sql)) {
$stmt->bind_param('i', $this->uid);
$stmt->execute();
$stmt->close();
}
}
}
return $status;
}

private function authenticateUsingOidSub($sub, $provider){
private function authenticateUsingOidSub($sub, $provider)
{
$status = false;
if($sub && $provider){
$sql = 'SELECT uid from usersthirdpartyauth WHERE subUuid = ? AND provider = ?';
if($stmt = $this->conn->prepare($sql)){
if($stmt->bind_param('ss', $sub, $provider)){
if ($sub && $provider) {
$sql = 'SELECT uid from usersthirdpartyauth WHERE subUuid = ? AND provider = ?';
if ($stmt = $this->conn->prepare($sql)) {
if ($stmt->bind_param('ss', $sub, $provider)) {
$stmt->execute();
$stmt->bind_result($this->uid);
$stmt->fetch();
$stmt->fetch();
$stmt->close();
}
else echo 'error binding parameters: '.$stmt->error;
} else echo 'error binding parameters: ' . $stmt->error;
}
if ($this->uid) {
$sql = 'SELECT uid, firstname, username FROM users WHERE (uid = ?)';
if ($stmt = $this->conn->prepare($sql)) {
if ($stmt->bind_param('i', $this->uid)) {
$stmt->execute();
$stmt->bind_result($this->uid, $this->displayName, $this->userName);
if ($stmt->fetch()) $status = true;
$stmt->close();
} else echo 'error binding parameters: ' . $stmt->error;
} else echo 'error preparing statement: ' . $this->conn->error;
}
if($this->uid){
$sql = 'SELECT uid, firstname, username FROM users WHERE (uid = ?)';
if($stmt = $this->conn->prepare($sql)){
if($stmt->bind_param('i', $this->uid)){
$stmt->execute();
$stmt->bind_result($this->uid, $this->displayName, $this->userName);
if($stmt->fetch()) $status = true;
$stmt->close();
}
else echo 'error binding parameters: '.$stmt->error;
}
else echo 'error preparing statement: '.$this->conn->error;
}
}
return $status;
}

public function linkLocalUserOidSub($email, $sub, $provider){
if($email && $sub && $provider){
$sql = 'SELECT u.uid, oid.subUuid, oid.provider from users u LEFT join usersthirdpartyauth oid ON u.uid = oid.uid
public function linkThirdPartySid($thirdparty_sid, $local_sid, $ip)
{
$sql = 'INSERT INTO usersthirdpartysessions(thirdparty_id, localsession_id, ipaddr) VALUES (?, ?, ?)';
if ($stmt = $this->conn->prepare($sql)) {
if ($stmt->bind_param('sss', $thirdparty_sid, $local_sid, $ip)) {
$stmt->execute();
//if($stmt->error){
//}
$stmt->close();
}
}
}

public function linkLocalUserOidSub($email, $sub, $provider)
{
if ($email && $sub && $provider) {
$sql = 'SELECT u.uid, oid.subUuid, oid.provider from users u LEFT join usersthirdpartyauth oid ON u.uid = oid.uid
WHERE u.email = ?';
if($stmt = $this->conn->prepare($sql)){
if($stmt->bind_param('s', $email)){
if ($stmt = $this->conn->prepare($sql)) {
if ($stmt->bind_param('s', $email)) {
$stmt->execute();
$results = mysqli_stmt_get_result($stmt);
$stmt->close();
}
if ($results->num_rows < 1){
if ($results->num_rows < 1) {
//Local user does not exist
throw new Exception ("User does not exist in symbiota database <ERR/>");
}
else {
if($results->num_rows == 1){
throw new Exception("User does not exist in symbiota database <ERR/>");
} else {
if ($results->num_rows == 1) {
$row = $results->fetch_array(MYSQLI_ASSOC);
if (($row['provider'] == '' && $row['subUuid'] == '') || ($row['provider'] && $row['provider'] !== $provider)){
//found existing user. add 3rdparty auth info
if (($row['provider'] == '' && $row['subUuid'] == '') || ($row['provider'] && $row['provider'] !== $provider)) {
//found existing user. add 3rdparty auth info
$sql = 'INSERT INTO usersthirdpartyauth (uid, subUuid, provider) VALUES(?,?,?)';
$this->resetConnection();
if($stmt = $this->conn->prepare($sql)) {
if ($stmt = $this->conn->prepare($sql)) {
$stmt->bind_param('iss', $row['uid'], $sub, $provider);
$stmt->execute();
}
$this->uid = $row['uid'];
return true;
}

}
else if($results->num_rows > 1){
} else if ($results->num_rows > 1) {
$uidPlaceholder = '';
while ($row = $results->fetch_array(MYSQLI_ASSOC)) {
$uidPlaceholder = $row['uid']; // assumes one-to-one relationship between user and email address
if ($row['provider'] == $provider && $row['subUuid'] !== $sub){
if ($row['provider'] == $provider && $row['subUuid'] !== $sub) {
return false; // current assumption is that if this happens, the subUuid is not kosher.
// If this assumption is ever violated, one solution would be to purge relevant rows from usersthirdpartyauth
}
else continue;
} else continue;
}
// Provider not found - handle adding new entry to usersthirdpartyauth table
$sql = 'INSERT INTO usersthirdpartyauth (uid, subUuid, provider) VALUES(?,?,?)';
$this->resetConnection();
if($stmt = $this->conn->prepare($sql)) {
if ($stmt = $this->conn->prepare($sql)) {
$stmt->bind_param('iss', $uidPlaceholder, $sub, $provider);
$stmt->execute();
}
$this->uid = $row['uid'];
return true;

}
}
}
}
}

public function lookupLocalSessionIDWithThirdPartySid($thirdparty_sid)
{
$sql = 'SELECT localsession_id FROM usersthirdpartysessions WHERE thirdparty_id = ?';
$localSessionID = '';
if ($stmt = $this->conn->prepare($sql)) {
if ($stmt->bind_param('s', $thirdparty_sid)) {
$stmt->execute();
$stmt->bind_result($localSessionID);
$stmt->fetch();
$stmt->close();
}
}
return $localSessionID;
}

public function forceLogout($targetSessionId)
{
session_write_close();
session_id($targetSessionId);
session_start();
$_SESSION['force_logout'] = true;
}
}
15 changes: 7 additions & 8 deletions config/auth_config_template.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<?php
$providerUrls = array('oid' => 'https://login.microsoftonline.com/AddMore/v2.0', 'google' => 'foo');
$clientIds = array('oid' => 'someGuid', 'google' => 'foo');
$clientSecrets = array('oid' => 'someGuid', 'google' => 'foo');
$callBackRedirect = 'https://' . $SERVER_HOST . $CLIENT_ROOT . '/profile/authCallback.php';

$PROVIDER_URLS = array('oid' => 'https://login.microsoftonline.com/AddMore/v2.0', 'google' => 'foo');
$CLIENT_IDS = array('oid' => 'someGuid', 'google' => 'foo');
$CLIENT_SECRETS = array('oid' => 'someGuid', 'google' => 'foo');
$CALLBACK_REDIRECT = '/profile/authCallback.php';
$LOGOUT_REDIRECT = '/profile/logout.php'; //@TODO add as auth provider's logout callback URL
// Needed for local Dev Env Only
// $shouldUpgradeInsecureRequests = false; // this needs to be commented in if you're developing locally without ssl enabled
// $shouldVerifyPeers = false;
?>
// $SHOULD_UPGRADE_INSECURE_REQUESTS = false; // this needs to be commented in if you're developing locally without ssl enabled
// $SHOULD_VERIFY_PEERS = false;
1 change: 1 addition & 0 deletions config/schema/3.0/dev/3.3-usersthirdpartysessions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE TABLE `usersthirdpartysessions` ( `thirdparty_id` varchar(255) NOT NULL, `localsession_id` varchar(255) NOT NULL, `ipaddr` varchar(255) NOT NULL, `timestamp` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`thirdparty_id`,`localsession_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci
9 changes: 8 additions & 1 deletion config/symbbase.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@
include_once($SERVER_ROOT . '/classes/utilities/Encryption.php');
include_once($SERVER_ROOT . '/classes/ProfileManager.php');

$pHandler = null;
//Check session data to see if signed in

if (isset($_SESSION['force_logout'])){
if(!isset($pHandler)) $pHandler = new ProfileManager();
$pHandler->reset();
unset($_SESSION['force_logout']);
}
$pHandler = null;

$PARAMS_ARR = Array(); //params => 'un=egbot&dn=Edward&uid=301'
$USER_RIGHTS = Array();
if(isset($_SESSION['userparams'])) $PARAMS_ARR = $_SESSION['userparams'];
Expand Down
2 changes: 1 addition & 1 deletion config/symbini_template.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
$ACTIVATE_GEOLOCATE_TOOLKIT = 0; //Activates GeoLocate Toolkit located within the Processing Toolkit menu items
$SEARCH_BY_TRAITS = 0; //Activates search fields for searching by traits (if trait data have been encoded): 0 = trait search off; any number of non-zeros separated by commas (e.g., '1,6') = trait search on for the traits with these id numbers in table tmtraits.
$CALENDAR_TRAIT_PLOTS = 0; //Activates polar plots, in taxon profile, of the trait states listed: 0 = no plot; any number of non-zeros separated by commas (e.g., '1,6') = plots appear for the trait states with these id numbers (in table tmstates).

//$AUTH_PROVIDER = 'oid'; //Activate Third Party Authentication using openID Connect (Addiotnal paramters defined in auth_config.php). Leave this commented out if not in use;
$IGSN_ACTIVATION = 0;

//$SMTP_ARR = array('host'=>'','port'=>587,'username'=>'','password'=>'','timeout'=>60); //Host is requiered, others are optional and can be removed
Expand Down
1 change: 0 additions & 1 deletion content/lang/profile/userprofile.en.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
$LANG['SUBMIT_EDITS'] = 'Submit Edits';
$LANG['SURE_DELETE'] = 'Are you sure you want to delete profile?';
$LANG['DELETE_PROF'] = 'Delete Profile';
$LANG['CHANGE_PWORD'] = 'Change Password';
$LANG['CURRENT_PWORD'] = 'Current Password';
$LANG['PWORD_AGAIN'] = 'New Password Again';
$LANG['CHANGE_USERNAME'] = 'Change Username';
Expand Down
1 change: 0 additions & 1 deletion content/lang/profile/userprofile.es.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
$LANG['SUBMIT_EDITS'] = 'Enviar Ediciones';
$LANG['SURE_DELETE'] = 'Está seguro que quiere eliminar el perfil?';
$LANG['DELETE_PROF'] = 'Eliminar Perfil';
$LANG['CHANGE_PWORD'] = 'Cambiar Contraseña';
$LANG['CURRENT_PWORD'] = 'Contraseña Actual';
$LANG['PWORD_AGAIN'] = 'Nueva Contraseña de Nuevo';
$LANG['CHANGE_USERNAME'] = 'Cambiar Usuario';
Expand Down
1 change: 0 additions & 1 deletion content/lang/profile/userprofile.fr.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
$LANG['SUBMIT_EDITS'] = 'Soumettre Modifications';
$LANG['SURE_DELETE'] = 'Êtes-vous sûr de vouloir supprimer Profil?';
$LANG['DELETE_PROF'] = 'Supprimer Profil';
$LANG['CHANGE_PWORD'] = 'Changer Mot de Passe';
$LANG['CURRENT_PWORD'] = 'Mot de Passe Actuel';
$LANG['PWORD_AGAIN'] = 'Mot de Passe Actuel à Nouveau';
$LANG['CHANGE_USERNAME'] = "Changer de Nom d'Utilisateur";
Expand Down
11 changes: 6 additions & 5 deletions docs/third_party_auth_setup.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: "Setting up Symbiota with Third Party Authentication"
date: 2024-02-19
lastmod: 2024-02-19
lastmod: 2025-02-13
icon: "ti-marker-alt"
weight: 2
authors: ["Mark Fisher"]
authors: ["Mark Fisher", "Gregory Post"]
description: "Learn to configure a Symbiota portal for third party authentication"
type: "docs"
---
Expand All @@ -14,15 +14,16 @@ type: "docs"
## This guide contains instructions for users to configure a Symbiota portal to leverage third party authentication platforms.

1. If you have not installed your Symbiota portal as described in the [installation instructions](https://github.com/BioKIC/Symbiota/blob/master/docs/INSTALL.md), complete the installation before proceeding.
2. If you are implementing this on a pre-existing Symbiota portal, you will need to load the new schema from <SymbiotaBaseFolder>/config/schema/3.1/TODO_PATCH_NAME.sql in order to establish the `usersthirdpartyauth` table in the database. Refer to the [installation instructions](https://github.com/BioKIC/Symbiota/blob/master/docs/INSTALL.md) for how to load database schemas.
2. If you are implementing this on a pre-existing Symbiota portal, you will need to load the following new tables from patch files in <SymbiotaBaseFolder>config/schema/... in order to establish the `usersthirdpartyauth` and `usersthirdpartysessions` tables in the database. Refer to the [installation instructions](https://github.com/BioKIC/Symbiota/blob/master/docs/INSTALL.md) for how to load database schemas.
3. Copy the config/auth_config_template.php file into the same config/ directory and rename it as auth_config.php: `cp config/auth_config_template.php config/auth_config.php`.
4. Obtain a provider URL, client ID, and client secret from your desired third party provider, such as [Microsoft EntraID](https://www.microsoft.com/en-us/security/business/microsoft-entra).
5. Modify the newly-created config/auth_config.php file to include the new values obtained in the previous step.
6. Designate a string value for `$AUTH_PROVIDER` in config/symbini.php. It doesn't matter what the string value is as long as it matches the keys in the associated arrays in config/auth_config.php corresponding to your desired provider. For instance, by default, the value in the symbini_template.php file is `'oid'`, which you will see corresponds to the key `'oid'` in the `$providerUrls`, `$clientIds`, and `$clientSecrets` arrays in config/auth_config.php.
6. Designate a string value for `$AUTH_PROVIDER` in config/symbini.php. It doesn't matter what the string value is as long as it matches the keys in the associated arrays in config/auth_config.php corresponding to your desired provider. For instance, by default, the value in the symbini_template.php file is `'oid'`, which you will see corresponds to the key `'oid'` in the `$PROVIDER_URLS`, `$CLIENT_IDS`, and `$CLIENT_SECRETS` arrays in config/auth_config.php.
7. Currently, the ability to allow for multiple authentication providers and/or protocols has not been implemented. If you are interested in having this ability on your Symbiota portal and/or are interested in developing it yourself, please [report a new issue](https://github.com/BioKIC/Symbiota/issues/new) about it and coordinate with the current Symbiota development team. When such infrastructure is in place to allow this, the previous three steps can be repeated for as many different providers as desired.
8. You may wish to use a non-OIDC authentication procotol. This is currently not supported, but if so, you will need to create a new login file similar to profile/openIdAuth.php and a callback file similar to /profile/authCallback.php. If you do, you will in turn need to change the value of `$LOGIN_ACTION_PAGE` in symbini.php and the value of `$callBackRedirect` in config/auth_config.php to reflect the paths of the newly-created files.
8. You may wish to use a non-OIDC authentication procotol. This is currently not supported, but if so, you will need to create a new login file similar to profile/openIdAuth.php and a callback file similar to /profile/authCallback.php. If you do, you will in turn need to change the value of `$LOGIN_ACTION_PAGE` in symbini.php and the value of `$CALLBACK_REDIRECT` in config/auth_config.php to reflect the paths of the newly-created files.
9. In your config/symbini.php file, make sure that the value of `$THIRD_PARTY_OID_AUTH_ENABLED` is set to `true`.
10. Depending on the needs of your portal, you may want to enable or disable native Symbiota login and/or public user creation (where any user can create their own account). These features can be enabled or disabled by designating the values of `$SYMBIOTA_LOGIN_ENABLED` and `$SHOULD_BE_ABLE_TO_CREATE_PUBLIC_USER`, respectively, in config/symbini.php.
11. Enjoy the new authentication workflow on your portal.
12. Make sure that the thirdparty auth provider login callback goes to your instance's URL/profile/authCallback.php and that its logout callback goes to your instance's URL/profile/logout.php.

Development and testing were performed using the Microsoft EntraID provider and the OpenID Connect (OIDC) protocol in conjunction with the [Jumbojett\OpenIDConnectClient library](https://github.com/jumbojett/OpenID-Connect-PHP) (note the software requirements listed in this library). If you are using other providers or protocols instead and hit any snags, please [report any issues](https://github.com/BioKIC/Symbiota/issues/new) or even [contribute your improvements to the codebase](https://github.com/BioKIC/Symbiota/blob/master/docs/CONTRIBUTING.md).
Loading