1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 09:49:30 +02:00
ampache/lib/class/auth.class.php
Phyks (Lucas Verney) e86ea9a099 Fix copyright date
2016-08-01 22:02:00 +02:00

461 lines
16 KiB
PHP

<?php
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
/**
*
* LICENSE: GNU Affero General Public License, version 3 (AGPLv3)
* Copyright 2001 - 2016 Ampache.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
*
* This class handles all of the session related stuff in Ampache
* it takes over for the vauth libs, and takes some stuff out of other
* classes where it didn't belong.
*/
class Auth
{
/**
* Constructor
*
* This should never be called
*/
private function __construct()
{
// Rien a faire
}
/**
* logout
*
* This is called when you want to log out and nuke your session.
* This is the function used for the Ajax logouts, if no id is passed
* it tries to find one from the session,
* @param string $key
* @param boolean $relogin
*/
public static function logout($key='', $relogin = true)
{
// If no key is passed try to find the session id
$key = $key ? $key : session_id();
// Nuke the cookie before all else
Session::destroy($key);
if ((!$relogin) && AmpConfig::get('logout_redirect')) {
$target = AmpConfig::get('logout_redirect');
} else {
$target = AmpConfig::get('web_path') . '/login.php';
}
// Do a quick check to see if this is an AJAXed logout request
// if so use the iframe to redirect
if (defined('AJAX_INCLUDE')) {
ob_end_clean();
ob_start();
xoutput_headers();
$results = array();
$results['rfc3514'] = '<script type="text/javascript">reloadRedirect("' . $target . '")</script>';
echo xoutput_from_array($results);
} else {
/* Redirect them to the login page */
header('Location: ' . $target);
}
exit;
}
/**
* login
*
* This takes a username and password and then returns the results
* based on what happens when we try to do the auth.
* @param string $username
* @param string $password
* @param boolean $allow_ui
* @return array
*/
public static function login($username, $password, $allow_ui = false)
{
$results = array();
foreach (AmpConfig::get('auth_methods') as $method) {
$function_name = $method . '_auth';
if (!method_exists('Auth', $function_name)) {
continue;
}
$results = self::$function_name($username, $password);
if ($results['success'] || ($allow_ui && !empty($results['ui_required']))) {
break;
}
}
return $results;
}
/**
* login_step2
*
* This process authenticate step2 for an auth module
* @param string $auth_mod
* @return array
*/
public static function login_step2($auth_mod)
{
$results = null;
if (in_array($auth_mod, AmpConfig::get('auth_methods'))) {
$function_name = $auth_mod . '_auth_2';
if (method_exists('Auth', $function_name)) {
$results = self::$function_name();
}
}
return $results;
}
/**
* mysql_auth
*
* This is the core function of our built-in authentication.
* @param string $username
* @param string $password
* @return array
*/
private static function mysql_auth($username, $password)
{
if (strlen($password) && strlen($username)) {
$sql = 'SELECT `password` FROM `user` WHERE `username` = ?';
$db_results = Dba::read($sql, array($username));
if ($row = Dba::fetch_assoc($db_results)) {
// Use SHA2 now... cooking with fire.
// For backwards compatibility we hash a couple of different
// variations of the password. Increases collision chances, but
// doesn't break things.
// FIXME: Break things in the future.
$hashed_password = array();
$hashed_password[] = hash('sha256', $password);
$hashed_password[] = hash('sha256',
Dba::escape(stripslashes(htmlspecialchars(strip_tags($password)))));
// Automagically update the password if it's old and busted.
if ($row['password'] == $hashed_password[1] &&
$hashed_password[0] != $hashed_password[1]) {
$user = User::get_from_username($username);
$user->update_password($password);
}
if (in_array($row['password'], $hashed_password)) {
return array(
'success' => true,
'type' => 'mysql',
'username' => $username
);
}
}
}
return array(
'success' => false,
'error' => 'MySQL login attempt failed'
);
}
/**
* pam_auth
*
* Check to make sure the pam_auth function is implemented (module is
* installed), then check the credentials.
* @param string $username
* @param string $password
* @return array
*/
private static function pam_auth($username, $password)
{
$results = array();
if (!function_exists('pam_auth')) {
$results['success'] = false;
$results['error'] = 'The PAM PHP module is not installed';
return $results;
}
$password = scrub_in($password);
if (pam_auth($username, $password)) {
$results['success'] = true;
$results['type'] = 'pam';
$results['username'] = $username;
} else {
$results['success'] = false;
$results['error'] = 'PAM login attempt failed';
}
return $results;
}
/**
* external_auth
*
* Calls an external program compatible with mod_authnz_external
* such as pwauth.
* @param string $username
* @param string $password
* @return array
*/
private static function external_auth($username, $password)
{
$authenticator = AmpConfig::get('external_authenticator');
if (!$authenticator) {
return array(
'success' => false,
'error' => 'No external authenticator configured'
);
}
//FIXME: should we do input sanitization?
$proc = proc_open($authenticator,
array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w')
), $pipes);
if (is_resource($proc)) {
fwrite($pipes[0], $username . "\n" . $password . "\n");
fclose($pipes[0]);
fclose($pipes[1]);
if ($stderr = fread($pipes[2], 8192)) {
debug_event('external_auth', "fread error: " . $stderr, 5);
}
fclose($pipes[2]);
} else {
return array(
'success' => false,
'error' => 'Failed to run external authenticator'
);
}
if (proc_close($proc) == 0) {
return array(
'success' => true,
'type' => 'external',
'username' => $username
);
}
return array(
'success' => false,
'error' => 'The external authenticator did not accept the login'
);
}
/**
* ldap_auth
* @param string $username
* @param string $password
* @return array
*/
private static function ldap_auth($username, $password)
{
return LDAP::auth($username, $password);
}
/**
* http_auth
* This auth method relies on HTTP auth from the webserver
*
* @param string $username
* @param string $password
* @return array
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
private static function http_auth($username, $password)
{
$results = array();
if (($_SERVER['REMOTE_USER'] == $username) ||
($_SERVER['HTTP_REMOTE_USER'] == $username)) {
$results['success'] = true;
$results['type'] = 'http';
$results['username'] = $username;
$results['name'] = $username;
$results['email'] = '';
} else {
$results['success'] = false;
$results['error'] = 'HTTP auth login attempt failed';
}
return $results;
} // http_auth
/**
* openid_auth
* Authenticate user with OpenID
*
* @param string $username
* @param string $password
* @return array
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
private static function openid_auth($username, $password)
{
$results = array();
// Username contains the openid url. We don't care about password here.
$website = $username;
if (strpos($website, 'http://') === 0 || strpos($website, 'https://') === 0) {
$consumer = Openid::get_consumer();
if ($consumer) {
$auth_request = $consumer->begin($website);
if ($auth_request) {
$sreg_request = Auth_OpenID_SRegRequest::build(
// Required
array('nickname'),
// Optional
array('fullname', 'email')
);
if ($sreg_request) {
$auth_request->addExtension($sreg_request);
}
$pape_request = new Auth_OpenID_PAPE_Request(Openid::get_policies());
if ($pape_request) {
$auth_request->addExtension($pape_request);
}
// Redirect the user to the OpenID server for authentication.
// Store the token for this authentication so we can verify the response.
// For OpenID 1, send a redirect. For OpenID 2, use a Javascript
// form to send a POST request to the server.
if ($auth_request->shouldSendRedirect()) {
$redirect_url = $auth_request->redirectURL(AmpConfig::get('web_path'), Openid::get_return_url());
if (Auth_OpenID::isFailure($redirect_url)) {
$results['success'] = false;
$results['error'] = 'Could not redirect to server: ' . $redirect_url->message;
} else {
// Send redirect.
debug_event('auth', 'OpenID 1: redirecting to ' . $redirect_url, '5');
header("Location: " . $redirect_url);
}
} else {
// Generate form markup and render it.
$form_id = 'openid_message';
$form_html = $auth_request->htmlMarkup(AmpConfig::get('web_path'), Openid::get_return_url(), false, array('id' => $form_id));
if (Auth_OpenID::isFailure($form_html)) {
$results['success'] = false;
$results['error'] = 'Could not render authentication form.';
} else {
debug_event('auth', 'OpenID 2: javascript redirection code to OpenID form.', '5');
// First step is a success, UI interaction required.
$results['success'] = false;
$results['ui_required'] = $form_html;
}
}
} else {
debug_event('auth', $website . ' is not a valid OpenID.', '3');
$results['success'] = false;
$results['error'] = 'Not a valid OpenID.';
}
} else {
debug_event('auth', 'Cannot initialize OpenID resources.', '3');
$results['success'] = false;
$results['error'] = 'Cannot initialize OpenID resources.';
}
} else {
debug_event('auth', 'Skipped OpenID authentication: missing scheme in ' . $website . '.', '3');
$results['success'] = false;
$results['error'] = 'Missing scheme in OpenID.';
}
return $results;
}
/**
* openid_auth_2
* Authenticate user with OpenID, step 2
* @return array
*/
private static function openid_auth_2()
{
$results = array();
$results['type'] = 'openid';
$consumer = Openid::get_consumer();
if ($consumer) {
$response = $consumer->complete(Openid::get_return_url());
if ($response->status == Auth_OpenID_CANCEL) {
$results['success'] = false;
$results['error'] = 'OpenID verification cancelled.';
} else {
if ($response->status == Auth_OpenID_FAILURE) {
$results['success'] = false;
$results['error'] = 'OpenID authentication failed: ' . $response->message;
} else {
if ($response->status == Auth_OpenID_SUCCESS) {
// Extract the identity URL and Simple Registration data (if it was returned).
$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
$sreg = $sreg_resp->contents();
$results['website'] = $response->getDisplayIdentifier();
if (@$sreg['email']) {
$results['email'] = $sreg['email'];
}
if (@$sreg['nickname']) {
$results['username'] = $sreg['nickname'];
}
if (@$sreg['fullname']) {
$results['name'] = $sreg['fullname'];
}
$users = User::get_from_website($results['website']);
if (count($users) > 0) {
if (count($users) == 1) {
$user = new User($users[0]);
$results['success'] = true;
$results['username'] = $user->username;
} else {
// Several users for the same website/openid? Allowed but stupid, try to get a match on username.
// Should we make website field unique?
foreach ($users as $id) {
$user = new User($id);
if ($user->username == $results['username']) {
$results['success'] = true;
$results['username'] = $user->username;
}
}
}
} else {
// Don't return success if an user already exists for this username but don't have this openid identity as website
$user = User::get_from_username($results['username']);
if ($user->id) {
$results['success'] = false;
$results['error'] = 'No user associated to this OpenID and username already taken.';
} else {
$results['success'] = true;
$results['error'] = 'No user associated to this OpenID.';
}
}
}
}
}
}
return $results;
}
}