diff options
Diffstat (limited to 'inc/shibauth.inc.php')
-rw-r--r-- | inc/shibauth.inc.php | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/inc/shibauth.inc.php b/inc/shibauth.inc.php new file mode 100644 index 0000000..6ae3a89 --- /dev/null +++ b/inc/shibauth.inc.php @@ -0,0 +1,202 @@ +<?php + +class ShibAuth +{ + + /** + * Log user into master-server using the data provided by the current shibboleth session + * @param ?string $accessCode optional one-time access code to retreive session data via thrift + * @return array{status: string, firstName: string, lastName: string, mail: string, token: string, sessionId: string, userId: string, organizationId: string, url: string, error: string} + */ + private static function loginInternal(?string $accessCode = null): array + { + if ($accessCode !== null) { + $entrop = strlen(count_chars($accessCode, 3)); + if (strlen($accessCode) < 32 || strlen($accessCode) > 64 || $entrop < 10) { + return ['status' => 'error', 'error' => 'Malformed accessCode']; + } + } + // Handle + if (empty($_SERVER['persistent-id'])) { + // No persistent id given, should not happen! + file_put_contents('/tmp/shib-nopid-' . time() . '-' . $_SERVER['REMOTE_ADDR'] . '.txt', print_r($_SERVER, true)); + return ['status' => 'error', 'error' => 'Shibboleth metadata missing!']; + } + // Query database for user + // First, use persistent-id as-is + $shibId = [ md5($_SERVER['persistent-id']) ]; + // ... but we might have multiple persistent IDs, split + if (strpos($_SERVER['persistent-id'], ';') !== false) { + foreach (explode(';', $_SERVER['persistent-id']) as $s) { + if (empty($s)) + continue; + $shibId[] = md5($s); + } + } + // Figure out role + if (strpos(";{$_SERVER['entitlement']};", CONFIG_ENTITLEMENT) !== false) { + $role = 'TUTOR'; + } else if (strpos(";{$_SERVER[CONFIG_SCOPED_AFFILIATION]};", ';employee@') !== false + || strpos(";{$_SERVER[CONFIG_SCOPED_AFFILIATION]};", ';staff@') !== false + || strpos(";{$_SERVER[CONFIG_SCOPED_AFFILIATION]};", ';faculty@') !== false) { + $role = 'TUTOR'; + } else { + file_put_contents('/tmp/shib-student-' . time() . '-' . $_SERVER['REMOTE_ADDR'] . '.txt', print_r($_SERVER, true)); + $role = 'STUDENT'; + // NEW: Ignore students for now + return [ + 'status' => 'error', + 'error' => "Sie wurden als Student eingestuft und können sich daher nicht an der " . CONFIG_SUITE . "-Suite anmelden." + . "\nFalls Ihr Nutzerkonto kein Studentenkonto ist stellen Sie sicher, dass Ihr IdP für berechtigte" + . "\nAccounts entweder das " . CONFIG_SUITE . "-Entitlement ausliefert, oder das Attribut " . CONFIG_SCOPED_AFFILIATION + . "\nausgeliefert wird, und es entweder 'employee@..', 'staff@..' oder 'faculty@..' enthält." + . "\n\nMehr Informationen finden Sie unter " . CONFIG_HELPURL + ]; + // end IGNORE STUDENTS + } + // Now we have an array of all persistent-ids, plus the raw string; see if any of those match + $user = Database::queryFirst("SELECT user.userid, user.organizationid, user.firstname, user.lastname, user.email " + . " FROM user " + . " INNER JOIN organization USING (organizationid) " + . " WHERE user.shibid IN (:shibid) LIMIT 1", array('shibid' => $shibId)); + if ($user === false) { + // Not found, so we don't know which satellite to use + if ($role === 'STUDENT') { + $response['status'] = 'ok'; + $response['firstName'] = $_SERVER['givenName'] ?? 'Anonymous'; + $response['lastName'] = $_SERVER[CONFIG_SURNAME] ?? 'Student'; + $response['mail'] = $_SERVER['mail'] ?? 'void@none.invalid'; + $response['userId'] = $shibId; + // Try to figure out orgId + if (!isset($response['organizationId']) && isset($_SERVER[CONFIG_EPPN])) { + if (preg_match('/@(.+)$/', $_SERVER[CONFIG_EPPN], $out)) { + $out = Database::queryFirst("SELECT organizationid FROM organization_suffix WHERE suffix = :suffix", [ + 'suffix' => $out[1] + ]); + if ($out !== false) { + $response['organizationId'] = $out['organizationid']; + } + } + } + if (!isset($response['organizationId']) && isset($_SERVER[CONFIG_SCOPED_AFFILIATION])) { + if (preg_match('/(^|;)[^@]+@([^;]+)/', $_SERVER[CONFIG_SCOPED_AFFILIATION], $out)) { + $out = Database::queryFirst("SELECT organizationid FROM organization_suffix WHERE suffix = :suffix", [ + 'suffix' => $out[2] + ]); + if ($out !== false) { + $response['organizationId'] = $out['organizationid']; + } + } + } + // This one we send to the running master server handler + $rpc = $response; + $rpc['role'] = $role; + if (isset($response['organizationId']) && $accessCode === null) { + $response['satellites2'] = self::getSatelliteList($response['organizationId']); + } + } else { + $response['status'] = 'unregistered'; + $response['error'] = 'Sie müssen sich erst für die Nutzung von ' . CONFIG_SUITE . ' registrieren'; + } + $response['id'] = $shibId; + $response['url'] = CONFIG_MASTERWEBIF; + file_put_contents('/tmp/shib-unreg-' . time() . '-' . $_SERVER['REMOTE_ADDR'] . '.txt', print_r($_SERVER, true)); + } else { + /* + if (in_array($shibId, unserialize(CONFIG_ADMINS), true) || $shibId === '2fa5c3e020a5aca0cbf9a562268d5173-') { + $role = 'STUDENT'; + } + */ + // Found, see if we got personal information, either temporarily through metadata, or from database + $firstName = $user['firstname']; + $lastName = $user['lastname']; + $mail = $user['email']; + if (empty($firstName) && isset($_SERVER['givenName'])) + $firstName = trim($_SERVER['givenName']); + if (empty($lastName) && isset($_SERVER[CONFIG_SURNAME])) + $lastName = trim($_SERVER[CONFIG_SURNAME]); + if (empty($mail) && isset($_SERVER['mail'])) + $mail = trim($_SERVER['mail']); + // + $login = (empty($user['userid']) ? $shibId : $user['userid'] ); + if (empty($firstName) || empty($lastName) || empty($login)) { + // This means the user did not provide personal information on signup, nor does the IdP send them + $response['status'] = 'anonymous'; + $response['error'] = "Ihr IdP liefert nicht die erforderlichen Attribute\n" + . CONFIG_SURNAME . ', ' . 'givenName' . ', ' . 'email'; + } else { + // Seems ok! + // + $response['status'] = 'ok'; + $response['firstName'] = $firstName; + $response['lastName'] = $lastName; + $response['mail'] = $mail; + $response['userId'] = $user['userid']; + $response['organizationId'] = $user['organizationid']; + // This one we send to the running master server handler + $rpc = $response; + $rpc['userId'] = $login; + $rpc['role'] = $role; + // This one we only send to the user + $response['satellites2'] = self::getSatelliteList($user['organizationid']); + } + } + + if (isset($rpc)) { + if ($accessCode !== null) { + $rpc['accessCode'] = $accessCode; + } + $reply = RPC::submit($rpc); + if (preg_match('/^TOKEN:(\w+) SESSIONID:(\w+)$/', $reply, $out)) { + // For talking to the sat server, also referred to as userToken in Java + $response['token'] = $out[1]; + // For talking to the master server - not known by satellite server, referred to as masterToken in Java + $response['sessionId'] = $out[2]; + } else { + if (empty($rpc['mail'])) { + $reply .= ' (No email given)'; + } + if (empty($rpc['firstName'])) { + $reply .= ' (No first name given)'; + } + if (empty($rpc['lastName'])) { + $reply .= ' (No last name given)'; + } + if (empty($rpc['organizationId'])) { + $reply .= ' (No organization id found)'; + } + $response['error'] = $reply; + $response['status'] = 'error'; + } + } + return $response; + } + + public static function login(?string $accessCode = null): array + { + $res = self::loginInternal($accessCode); + if ($res['status'] !== 'ok' && isset($res['error']) && $accessCode !== null) { + RPC::submit(['status' => 'error', 'error' => $res['error'], 'accessCode' => $accessCode]); + } + return $res; + } + + private static function getSatelliteList($orgId) + { + // Determine satellite(s) + $res = Database::simpleQuery("SELECT satellitename, addresses, certsha256 FROM satellite" + . " WHERE organizationid = :organizationid AND userid IS NULL", array('organizationid' => $orgId)); + $sat2 = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $addrs = json_decode($row['addresses'], true); + if (!is_array($addrs) || empty($addrs)) + continue; + $sat2[$row['satellitename']] = array( + 'addresses' => $addrs, + 'certHash' => $row['certsha256'] + ); + } + return $sat2; + } + +}
\ No newline at end of file |