2018-01-10 19:06:00 -05:00
< ? php
2019-10-10 19:21:41 -04:00
2018-01-21 13:33:59 -05:00
/**
* @ file src / Module / Login . php
*/
2019-10-10 19:21:41 -04:00
2018-01-10 19:06:00 -05:00
namespace Friendica\Module ;
2018-07-19 22:15:21 -04:00
use Exception ;
2018-01-10 19:06:00 -05:00
use Friendica\BaseModule ;
2018-10-17 08:19:58 -04:00
use Friendica\Core\Authentication ;
2018-01-10 19:06:00 -05:00
use Friendica\Core\Config ;
2018-12-26 01:06:24 -05:00
use Friendica\Core\Hook ;
2018-01-21 13:33:59 -05:00
use Friendica\Core\L10n ;
2018-10-29 17:20:46 -04:00
use Friendica\Core\Logger ;
2018-10-31 10:35:50 -04:00
use Friendica\Core\Renderer ;
2019-05-13 00:55:26 -04:00
use Friendica\Core\Session ;
2018-10-13 14:02:04 -04:00
use Friendica\Core\System ;
2018-07-20 08:19:26 -04:00
use Friendica\Database\DBA ;
2018-01-10 19:06:00 -05:00
use Friendica\Model\User ;
2018-01-26 21:38:34 -05:00
use Friendica\Util\DateTimeFormat ;
2018-01-26 23:51:41 -05:00
use Friendica\Util\Network ;
2018-11-08 10:14:37 -05:00
use Friendica\Util\Strings ;
2018-01-24 21:08:45 -05:00
use LightOpenID ;
2018-01-10 19:06:00 -05:00
/**
* Login module
*
2018-09-15 19:28:38 -04:00
* @ author Hypolite Petovan < hypolite @ mrpetovan . com >
2018-01-10 19:06:00 -05:00
*/
class Login extends BaseModule
{
public static function content ()
{
$a = self :: getApp ();
if ( local_user ()) {
2018-10-19 14:11:27 -04:00
$a -> internalRedirect ();
2018-01-10 19:06:00 -05:00
}
2019-05-26 16:15:38 -04:00
return self :: form ( Session :: get ( 'return_path' ), intval ( Config :: get ( 'config' , 'register_policy' )) !== \Friendica\Module\Register :: CLOSED );
2018-01-10 19:06:00 -05:00
}
public static function post ()
{
2019-10-24 16:23:26 -04:00
$openid_identity = Session :: get ( 'openid_identity' );
$openid_server = Session :: get ( 'openid_server' );
2019-05-26 16:15:38 -04:00
$return_path = Session :: get ( 'return_path' );
2018-01-10 19:06:00 -05:00
session_unset ();
2019-05-26 16:15:38 -04:00
Session :: set ( 'return_path' , $return_path );
2019-01-13 13:03:13 -05:00
2018-01-10 19:06:00 -05:00
// OpenId Login
if (
2018-02-09 00:08:01 -05:00
empty ( $_POST [ 'password' ])
2019-10-10 19:21:41 -04:00
&& ( ! empty ( $_POST [ 'openid_url' ])
|| ! empty ( $_POST [ 'username' ]))
2018-01-10 19:06:00 -05:00
) {
2019-10-15 09:20:32 -04:00
$openid_url = trim (( $_POST [ 'openid_url' ] ? ? '' ) ? : $_POST [ 'username' ]);
2018-01-10 19:06:00 -05:00
2018-02-09 00:08:01 -05:00
self :: openIdAuthentication ( $openid_url , ! empty ( $_POST [ 'remember' ]));
}
2018-01-10 19:06:00 -05:00
2018-11-30 09:06:22 -05:00
if ( ! empty ( $_POST [ 'auth-params' ]) && $_POST [ 'auth-params' ] === 'login' ) {
2018-02-09 00:08:01 -05:00
self :: passwordAuthentication (
trim ( $_POST [ 'username' ]),
trim ( $_POST [ 'password' ]),
2019-10-24 16:23:26 -04:00
! empty ( $_POST [ 'remember' ]),
$openid_identity ,
$openid_server
2018-02-09 00:08:01 -05:00
);
}
}
2018-01-10 19:06:00 -05:00
2018-02-09 00:08:01 -05:00
/**
* Attempts to authenticate using OpenId
*
* @ param string $openid_url OpenID URL string
* @ param bool $remember Whether to set the session remember flag
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-02-09 00:08:01 -05:00
*/
private static function openIdAuthentication ( $openid_url , $remember )
{
$noid = Config :: get ( 'system' , 'no_openid' );
2018-10-13 14:02:04 -04:00
$a = self :: getApp ();
2018-02-09 00:08:01 -05:00
// if it's an email address or doesn't resolve to a URL, fail.
if ( $noid || strpos ( $openid_url , '@' ) || ! Network :: isUrlValid ( $openid_url )) {
notice ( L10n :: t ( 'Login failed.' ) . EOL );
2018-10-19 14:11:27 -04:00
$a -> internalRedirect ();
2018-01-10 19:06:00 -05:00
// NOTREACHED
}
2018-02-09 00:08:01 -05:00
// Otherwise it's probably an openid.
try {
2018-10-09 13:58:58 -04:00
$openid = new LightOpenID ( $a -> getHostName ());
2018-02-09 00:08:01 -05:00
$openid -> identity = $openid_url ;
2019-10-24 16:23:26 -04:00
Session :: set ( 'openid' , $openid_url );
Session :: set ( 'remember' , $remember );
2018-10-19 14:11:27 -04:00
$openid -> returnUrl = $a -> getBaseURL ( true ) . '/openid' ;
2019-10-24 16:23:26 -04:00
$openid -> optional = [ 'namePerson/friendly' , 'contact/email' , 'namePerson' , 'namePerson/first' , 'media/image/aspect11' , 'media/image/default' ];
2018-10-19 14:11:27 -04:00
System :: externalRedirect ( $openid -> authUrl ());
2018-02-09 00:08:01 -05:00
} catch ( Exception $e ) {
notice ( L10n :: t ( 'We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.' ) . '<br /><br >' . L10n :: t ( 'The error message was:' ) . ' ' . $e -> getMessage ());
}
}
2018-01-10 19:06:00 -05:00
2018-02-09 00:08:01 -05:00
/**
* Attempts to authenticate using login / password
*
2019-10-24 16:23:26 -04:00
* @ param string $username User name
* @ param string $password Clear password
* @ param bool $remember Whether to set the session remember flag
* @ param string $openid_identity OpenID identity
* @ param string $openid_server OpenID URL
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-02-09 00:08:01 -05:00
*/
2019-10-24 16:23:26 -04:00
private static function passwordAuthentication ( $username , $password , $remember , $openid_identity , $openid_server )
2018-02-09 00:08:01 -05:00
{
$record = null ;
$addon_auth = [
'username' => $username ,
'password' => $password ,
'authenticated' => 0 ,
'user_record' => null
];
2018-10-13 14:02:04 -04:00
$a = self :: getApp ();
2018-02-09 00:08:01 -05:00
/*
* An addon indicates successful login by setting 'authenticated' to non - zero value and returning a user record
* Addons should never set 'authenticated' except to indicate success - as hooks may be chained
* and later addons should not interfere with an earlier one that succeeded .
*/
2018-12-26 01:06:24 -05:00
Hook :: callAll ( 'authenticate' , $addon_auth );
2018-02-09 00:08:01 -05:00
try {
if ( $addon_auth [ 'authenticated' ]) {
$record = $addon_auth [ 'user_record' ];
2018-01-10 19:06:00 -05:00
2018-02-09 00:08:01 -05:00
if ( empty ( $record )) {
throw new Exception ( L10n :: t ( 'Login failed.' ));
}
} else {
2019-10-10 19:21:41 -04:00
$record = DBA :: selectFirst (
'user' ,
[],
2018-02-09 00:08:01 -05:00
[ 'uid' => User :: getIdFromPasswordAuthentication ( $username , $password )]
);
2018-01-10 19:06:00 -05:00
}
2018-02-09 00:08:01 -05:00
} catch ( Exception $e ) {
2019-01-07 15:32:06 -05:00
Logger :: warning ( 'authenticate: failed login attempt' , [ 'action' => 'login' , 'username' => Strings :: escapeTags ( $username ), 'ip' => $_SERVER [ 'REMOTE_ADDR' ]]);
2018-10-03 08:32:16 -04:00
info ( 'Login failed. Please check your credentials.' . EOL );
2018-10-19 14:11:27 -04:00
$a -> internalRedirect ();
2018-02-09 00:08:01 -05:00
}
2018-01-10 19:06:00 -05:00
2018-02-09 00:08:01 -05:00
if ( ! $remember ) {
2018-10-17 15:30:41 -04:00
Authentication :: setCookie ( 0 ); // 0 means delete on browser exit
2018-02-09 00:08:01 -05:00
}
2018-01-10 19:06:00 -05:00
2018-02-09 00:08:01 -05:00
// if we haven't failed up this point, log them in.
2019-05-26 16:15:38 -04:00
Session :: set ( 'remember' , $remember );
Session :: set ( 'last_login_date' , DateTimeFormat :: utcNow ());
2019-05-13 00:55:26 -04:00
2019-10-24 16:23:26 -04:00
if ( ! empty ( $openid_identity ) || ! empty ( $openid_server )) {
DBA :: update ( 'user' , [ 'openid' => $openid_identity , 'openidserver' => $openid_server ], [ 'uid' => $record [ 'uid' ]]);
}
2019-05-13 00:55:26 -04:00
Session :: setAuthenticatedForUser ( $a , $record , true , true );
2018-01-10 19:06:00 -05:00
2019-05-26 16:15:38 -04:00
$return_path = Session :: get ( 'return_path' , '' );
Session :: remove ( 'return_path' );
2018-02-09 00:08:01 -05:00
2018-10-19 17:55:11 -04:00
$a -> internalRedirect ( $return_path );
2018-01-10 19:06:00 -05:00
}
/**
* @ brief Tries to auth the user from the cookie or session
*
* @ todo Should be moved to Friendica\Core\Session when it ' s created
*/
public static function sessionAuth ()
{
2018-10-13 14:02:04 -04:00
$a = self :: getApp ();
2018-01-10 19:06:00 -05:00
// When the "Friendica" cookie is set, take the value to authenticate and renew the cookie.
if ( isset ( $_COOKIE [ " Friendica " ])) {
$data = json_decode ( $_COOKIE [ " Friendica " ]);
if ( isset ( $data -> uid )) {
2019-10-10 19:21:41 -04:00
$user = DBA :: selectFirst (
'user' ,
[],
2018-01-10 19:06:00 -05:00
[
'uid' => $data -> uid ,
'blocked' => false ,
'account_expired' => false ,
'account_removed' => false ,
'verified' => true ,
]
);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $user )) {
2019-10-11 00:48:13 -04:00
if ( ! hash_equals (
2019-10-10 19:21:41 -04:00
Authentication :: getCookieHashForUser ( $user ),
$data -> hash
2019-10-11 00:48:13 -04:00
)) {
2018-10-29 17:20:46 -04:00
Logger :: log ( " Hash for user " . $data -> uid . " doesn't fit. " );
2018-10-17 15:30:41 -04:00
Authentication :: deleteSession ();
2018-10-19 14:11:27 -04:00
$a -> internalRedirect ();
2018-01-10 19:06:00 -05:00
}
// Renew the cookie
// Expires after 7 days by default,
// can be set via system.auth_cookie_lifetime
$authcookiedays = Config :: get ( 'system' , 'auth_cookie_lifetime' , 7 );
2018-10-17 15:30:41 -04:00
Authentication :: setCookie ( $authcookiedays * 24 * 60 * 60 , $user );
2018-01-10 19:06:00 -05:00
// Do the authentification if not done by now
if ( ! isset ( $_SESSION ) || ! isset ( $_SESSION [ 'authenticated' ])) {
2019-05-13 00:55:26 -04:00
Session :: setAuthenticatedForUser ( $a , $user );
2018-01-10 19:06:00 -05:00
if ( Config :: get ( 'system' , 'paranoia' )) {
$_SESSION [ 'addr' ] = $data -> ip ;
}
}
}
}
}
2018-11-30 09:06:22 -05:00
if ( ! empty ( $_SESSION [ 'authenticated' ])) {
if ( ! empty ( $_SESSION [ 'visitor_id' ]) && empty ( $_SESSION [ 'uid' ])) {
2018-08-19 08:46:11 -04:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $_SESSION [ 'visitor_id' ]]);
if ( DBA :: isResult ( $contact )) {
self :: getApp () -> contact = $contact ;
2018-01-10 19:06:00 -05:00
}
}
2018-11-30 09:06:22 -05:00
if ( ! empty ( $_SESSION [ 'uid' ])) {
2018-01-10 19:06:00 -05:00
// already logged in user returning
$check = Config :: get ( 'system' , 'paranoia' );
// extra paranoia - if the IP changed, log them out
if ( $check && ( $_SESSION [ 'addr' ] != $_SERVER [ 'REMOTE_ADDR' ])) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Session address changed. Paranoid setting in effect, blocking session. ' .
2018-01-10 19:06:00 -05:00
$_SESSION [ 'addr' ] . ' != ' . $_SERVER [ 'REMOTE_ADDR' ]);
2018-10-17 15:30:41 -04:00
Authentication :: deleteSession ();
2018-10-19 14:11:27 -04:00
$a -> internalRedirect ();
2018-01-10 19:06:00 -05:00
}
2019-10-10 19:21:41 -04:00
$user = DBA :: selectFirst (
'user' ,
[],
2018-01-10 19:06:00 -05:00
[
'uid' => $_SESSION [ 'uid' ],
'blocked' => false ,
'account_expired' => false ,
'account_removed' => false ,
'verified' => true ,
]
);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $user )) {
2018-10-17 15:30:41 -04:00
Authentication :: deleteSession ();
2018-10-19 14:11:27 -04:00
$a -> internalRedirect ();
2018-01-10 19:06:00 -05:00
}
// Make sure to refresh the last login time for the user if the user
// stays logged in for a long time, e.g. with "Remember Me"
$login_refresh = false ;
2018-07-10 08:27:56 -04:00
if ( empty ( $_SESSION [ 'last_login_date' ])) {
2018-01-26 21:38:34 -05:00
$_SESSION [ 'last_login_date' ] = DateTimeFormat :: utcNow ();
2018-01-10 19:06:00 -05:00
}
2018-01-26 21:38:34 -05:00
if ( strcmp ( DateTimeFormat :: utc ( 'now - 12 hours' ), $_SESSION [ 'last_login_date' ]) > 0 ) {
$_SESSION [ 'last_login_date' ] = DateTimeFormat :: utcNow ();
2018-01-10 19:06:00 -05:00
$login_refresh = true ;
}
2019-05-13 00:55:26 -04:00
Session :: setAuthenticatedForUser ( $a , $user , false , false , $login_refresh );
2018-01-10 19:06:00 -05:00
}
}
}
/**
* @ brief Wrapper for adding a login box .
*
2019-01-06 16:06:53 -05:00
* @ param string $return_path The path relative to the base the user should be sent
* back to after login completes
* @ param bool $register If $register == true provide a registration link .
* This will most always depend on the value of config . register_policy .
* @ param array $hiddens optional
2018-01-10 19:06:00 -05:00
*
* @ return string Returns the complete html for inserting into the page
*
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-01-10 19:06:00 -05:00
* @ hooks 'login_hook' string $o
*/
2018-10-19 17:55:11 -04:00
public static function form ( $return_path = null , $register = false , $hiddens = [])
2018-01-10 19:06:00 -05:00
{
$a = self :: getApp ();
$o = '' ;
2019-10-24 16:23:26 -04:00
$noid = Config :: get ( 'system' , 'no_openid' );
if ( $noid ) {
Session :: remove ( 'openid_identity' );
Session :: remove ( 'openid_attributes' );
}
2018-01-10 19:06:00 -05:00
$reg = false ;
2019-07-15 09:11:21 -04:00
if ( $register && intval ( $a -> getConfig () -> get ( 'config' , 'register_policy' )) !== Register :: CLOSED ) {
2018-01-15 08:05:12 -05:00
$reg = [
2018-01-22 09:54:13 -05:00
'title' => L10n :: t ( 'Create a New Account' ),
2019-10-24 16:23:26 -04:00
'desc' => L10n :: t ( 'Register' ),
'url' => self :: getRegisterURL ()
2018-01-15 08:05:12 -05:00
];
2018-01-10 19:06:00 -05:00
}
2018-10-19 17:55:11 -04:00
if ( is_null ( $return_path )) {
$return_path = $a -> query_string ;
2018-01-10 19:06:00 -05:00
}
if ( local_user ()) {
2018-10-31 10:44:06 -04:00
$tpl = Renderer :: getMarkupTemplate ( 'logout.tpl' );
2018-01-10 19:06:00 -05:00
} else {
2018-10-31 10:35:50 -04:00
$a -> page [ 'htmlhead' ] .= Renderer :: replaceMacros (
2018-10-31 10:44:06 -04:00
Renderer :: getMarkupTemplate ( 'login_head.tpl' ),
2018-01-10 19:06:00 -05:00
[
2018-10-09 13:58:58 -04:00
'$baseurl' => $a -> getBaseURL ( true )
2018-01-10 19:06:00 -05:00
]
);
2018-10-31 10:44:06 -04:00
$tpl = Renderer :: getMarkupTemplate ( 'login.tpl' );
2018-10-19 17:55:11 -04:00
$_SESSION [ 'return_path' ] = $return_path ;
2018-01-10 19:06:00 -05:00
}
2019-10-24 16:23:26 -04:00
if ( ! empty ( Session :: get ( 'openid_identity' ))) {
$openid_title = L10n :: t ( 'Your OpenID: ' );
$openid_readonly = true ;
$identity = Session :: get ( 'openid_identity' );
$username_desc = L10n :: t ( 'Please enter your username and password to add the OpenID to your existing account.' );
} else {
$openid_title = L10n :: t ( 'Or login using OpenID: ' );
$openid_readonly = false ;
$identity = '' ;
$username_desc = '' ;
}
2018-10-31 10:35:50 -04:00
$o .= Renderer :: replaceMacros (
2018-01-10 19:06:00 -05:00
$tpl ,
[
2018-10-09 13:58:58 -04:00
'$dest_url' => self :: getApp () -> getBaseURL ( true ) . '/login' ,
2018-01-22 09:54:13 -05:00
'$logout' => L10n :: t ( 'Logout' ),
'$login' => L10n :: t ( 'Login' ),
2018-01-10 19:06:00 -05:00
2019-10-24 16:23:26 -04:00
'$lname' => [ 'username' , L10n :: t ( 'Nickname or Email: ' ), '' , $username_desc ],
2018-01-22 09:54:13 -05:00
'$lpassword' => [ 'password' , L10n :: t ( 'Password: ' ), '' , '' ],
'$lremember' => [ 'remember' , L10n :: t ( 'Remember me' ), 0 , '' ],
2018-01-10 19:06:00 -05:00
'$openid' => ! $noid ,
2019-10-24 16:23:26 -04:00
'$lopenid' => [ 'openid_url' , $openid_title , $identity , '' , $openid_readonly ],
2018-01-10 19:06:00 -05:00
'$hiddens' => $hiddens ,
'$register' => $reg ,
2018-01-22 09:54:13 -05:00
'$lostpass' => L10n :: t ( 'Forgot your password?' ),
'$lostlink' => L10n :: t ( 'Password Reset' ),
2018-01-10 19:06:00 -05:00
2018-01-22 09:54:13 -05:00
'$tostitle' => L10n :: t ( 'Website Terms of Service' ),
'$toslink' => L10n :: t ( 'terms of service' ),
2018-01-10 19:06:00 -05:00
2018-01-22 09:54:13 -05:00
'$privacytitle' => L10n :: t ( 'Website Privacy Policy' ),
'$privacylink' => L10n :: t ( 'privacy policy' ),
2018-01-10 19:06:00 -05:00
]
);
2018-12-26 01:06:24 -05:00
Hook :: callAll ( 'login_hook' , $o );
2018-01-10 19:06:00 -05:00
return $o ;
}
2019-10-24 16:23:26 -04:00
/**
* Get the URL to the register page and add OpenID parameters to it
*/
private static function getRegisterURL ()
{
if ( empty ( Session :: get ( 'openid_identity' ))) {
return 'register' ;
}
2019-10-28 16:38:53 -04:00
$args = [];
2019-10-29 09:26:54 -04:00
$attr = Session :: get ( 'openid_attributes' , []);
2019-10-24 16:23:26 -04:00
if ( is_array ( $attr ) && count ( $attr )) {
foreach ( $attr as $k => $v ) {
if ( $k === 'namePerson/friendly' ) {
$nick = Strings :: escapeTags ( trim ( $v ));
}
if ( $k === 'namePerson/first' ) {
$first = Strings :: escapeTags ( trim ( $v ));
}
if ( $k === 'namePerson' ) {
2019-10-28 16:38:53 -04:00
$args [ 'username' ] = Strings :: escapeTags ( trim ( $v ));
2019-10-24 16:23:26 -04:00
}
if ( $k === 'contact/email' ) {
2019-10-28 16:38:53 -04:00
$args [ 'email' ] = Strings :: escapeTags ( trim ( $v ));
2019-10-24 16:23:26 -04:00
}
if ( $k === 'media/image/aspect11' ) {
$photosq = bin2hex ( trim ( $v ));
}
if ( $k === 'media/image/default' ) {
$photo = bin2hex ( trim ( $v ));
}
}
}
if ( ! empty ( $nick )) {
2019-10-28 16:38:53 -04:00
$args [ 'nickname' ] = $nick ;
2019-10-24 16:23:26 -04:00
} elseif ( ! empty ( $first )) {
2019-10-28 16:38:53 -04:00
$args [ 'nickname' ] = $first ;
2019-10-24 16:23:26 -04:00
}
if ( ! empty ( $photosq )) {
2019-10-28 16:38:53 -04:00
$args [ 'photo' ] = $photosq ;
2019-10-24 16:23:26 -04:00
} elseif ( ! empty ( $photo )) {
2019-10-28 16:38:53 -04:00
$args [ 'photo' ] = $photo ;
2019-10-24 16:23:26 -04:00
}
2019-10-28 16:38:53 -04:00
$args [ 'openid_url' ] = Strings :: escapeTags ( trim ( Session :: get ( 'openid_identity' )));
2019-10-24 16:23:26 -04:00
2019-10-28 16:38:53 -04:00
return 'register?' . http_build_query ( $args );
2019-10-24 16:23:26 -04:00
}
2018-01-10 19:06:00 -05:00
}