2015-08-23 22:38:18 +02:00
< ? php
/**
* @ file include / security . php
*
* Some security related functions .
*/
/**
* @ param int $user_record The account_id
* @ param bool $login_initial default false
* @ param bool $interactive default false
* @ param bool $return
* @ param bool $update_lastlog
*/
function authenticate_success ( $user_record , $login_initial = false , $interactive = false , $return = false , $update_lastlog = false ) {
$_SESSION [ 'addr' ] = $_SERVER [ 'REMOTE_ADDR' ];
2016-02-28 12:11:12 +01:00
$lastlog_updated = false ;
2015-08-23 22:38:18 +02:00
if ( x ( $user_record , 'account_id' )) {
2016-04-17 16:29:18 +02:00
App :: $account = $user_record ;
2015-08-23 22:38:18 +02:00
$_SESSION [ 'account_id' ] = $user_record [ 'account_id' ];
$_SESSION [ 'authenticated' ] = 1 ;
$uid_to_load = ((( x ( $_SESSION , 'uid' )) && ( intval ( $_SESSION [ 'uid' ])))
? intval ( $_SESSION [ 'uid' ])
2016-04-17 16:29:18 +02:00
: intval ( App :: $account [ 'account_default_channel' ])
2015-08-23 22:38:18 +02:00
);
if ( $uid_to_load ) {
change_channel ( $uid_to_load );
}
2016-02-28 12:11:12 +01:00
if ( $login_initial || $update_lastlog ) {
q ( " update account set account_lastlog = '%s' where account_id = %d " ,
dbesc ( datetime_convert ()),
intval ( $_SESSION [ 'account_id' ])
);
2016-04-17 16:29:18 +02:00
App :: $account [ 'account_lastlog' ] = datetime_convert ();
2016-02-28 12:11:12 +01:00
$lastlog_updated = true ;
2016-04-17 16:29:18 +02:00
call_hooks ( 'logged_in' , App :: $account );
2016-02-28 12:11:12 +01:00
}
2015-08-23 22:38:18 +02:00
}
2016-02-28 12:11:12 +01:00
if (( $login_initial ) && ( ! $lastlog_updated )) {
2015-08-23 22:38:18 +02:00
call_hooks ( 'logged_in' , $user_record );
// might want to log success here
}
if ( $return || x ( $_SESSION , 'workflow' )) {
unset ( $_SESSION [ 'workflow' ]);
return ;
}
2016-04-17 16:29:18 +02:00
if (( App :: $module !== 'home' ) && x ( $_SESSION , 'login_return_url' ) && strlen ( $_SESSION [ 'login_return_url' ])) {
2015-08-23 22:38:18 +02:00
$return_url = $_SESSION [ 'login_return_url' ];
// don't let members get redirected to a raw ajax page update - this can happen
// if DHCP changes the IP address at an unfortunate time and paranoia is turned on
if ( strstr ( $return_url , 'update_' ))
$return_url = '' ;
unset ( $_SESSION [ 'login_return_url' ]);
2016-04-17 16:29:18 +02:00
goaway ( z_root () . '/' . $return_url );
2015-08-23 22:38:18 +02:00
}
/* This account has never created a channel. Send them to new_channel by default */
2016-04-17 16:29:18 +02:00
if ( App :: $module === 'login' ) {
2015-08-23 22:38:18 +02:00
$r = q ( " select count(channel_id) as total from channel where channel_account_id = %d and channel_removed = 0 " ,
2016-04-17 16:29:18 +02:00
intval ( App :: $account [ 'account_id' ])
2015-08-23 22:38:18 +02:00
);
if (( $r ) && ( ! $r [ 0 ][ 'total' ]))
goaway ( z_root () . '/new_channel' );
}
/* else just return */
}
/**
* @ brief Change to another channel with current logged - in account .
*
* @ param int $change_channel The channel_id of the channel you want to change to
*
* @ return bool | array false or channel record of the new channel
*/
function change_channel ( $change_channel ) {
$ret = false ;
if ( $change_channel ) {
2016-01-12 02:47:38 +01:00
2015-08-23 22:38:18 +02:00
$r = q ( " select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel_id = %d and channel_account_id = %d and channel_removed = 0 limit 1 " ,
intval ( $change_channel ),
intval ( get_account_id ())
);
// It's not there. Is this an administrator, and is this the sys channel?
if ( is_developer ()) {
if ( ! $r ) {
if ( is_site_admin ()) {
$r = q ( " select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel_id = %d and channel_system = 1 and channel_removed = 0 limit 1 " ,
intval ( $change_channel )
);
}
}
}
if ( $r ) {
$hash = $r [ 0 ][ 'channel_hash' ];
$_SESSION [ 'uid' ] = intval ( $r [ 0 ][ 'channel_id' ]);
2016-04-17 16:29:18 +02:00
App :: set_channel ( $r [ 0 ]);
2015-08-23 22:38:18 +02:00
$_SESSION [ 'theme' ] = $r [ 0 ][ 'channel_theme' ];
$_SESSION [ 'mobile_theme' ] = get_pconfig ( local_channel (), 'system' , 'mobile_theme' );
date_default_timezone_set ( $r [ 0 ][ 'channel_timezone' ]);
$ret = $r [ 0 ];
}
$x = q ( " select * from xchan where xchan_hash = '%s' limit 1 " ,
dbesc ( $hash )
);
if ( $x ) {
$_SESSION [ 'my_url' ] = $x [ 0 ][ 'xchan_url' ];
2016-04-17 16:29:18 +02:00
$_SESSION [ 'my_address' ] = $r [ 0 ][ 'channel_address' ] . '@' . substr ( z_root (), strpos ( z_root (), '://' ) + 3 );
2015-08-23 22:38:18 +02:00
2016-04-17 16:29:18 +02:00
App :: set_observer ( $x [ 0 ]);
App :: set_perms ( get_all_perms ( local_channel (), $hash ));
2015-08-23 22:38:18 +02:00
}
if ( ! is_dir ( 'store/' . $r [ 0 ][ 'channel_address' ]))
@ os_mkdir ( 'store/' . $r [ 0 ][ 'channel_address' ], STORAGE_DEFAULT_PERMISSIONS , true );
}
return $ret ;
}
/**
2016-01-12 02:47:38 +01:00
* @ brief Creates an additional SQL where statement to check permissions .
2015-08-23 22:38:18 +02:00
*
* @ param int $owner_id
2016-01-12 02:47:38 +01:00
* @ param bool $remote_observer - if unset use current observer
2015-08-23 22:38:18 +02:00
*
* @ return string additional SQL where statement
*/
2016-01-12 02:47:38 +01:00
2016-03-20 08:06:33 +01:00
function permissions_sql ( $owner_id , $remote_observer = null , $table = '' ) {
2015-08-23 22:38:18 +02:00
$local_channel = local_channel ();
/**
* Construct permissions
*
* default permissions - anonymous user
*/
2016-03-20 08:06:33 +01:00
if ( $table )
$table .= '.' ;
$sql = " AND { $table } allow_cid = ''
AND { $table } allow_gid = ''
AND { $table } deny_cid = ''
AND { $table } deny_gid = ''
2015-08-23 22:38:18 +02:00
" ;
/**
* Profile owner - everything is visible
*/
if (( $local_channel ) && ( $local_channel == $owner_id )) {
$sql = '' ;
}
/**
* Authenticated visitor . Unless pre - verified ,
* check that the contact belongs to this $owner_id
* and load the groups the visitor belongs to .
* If pre - verified , the caller is expected to have already
* done this and passed the groups into this function .
*/
else {
2016-02-28 12:11:12 +01:00
$observer = (( ! is_null ( $remote_observer )) ? $remote_observer : get_observer_hash ());
2015-08-23 22:38:18 +02:00
if ( $observer ) {
$groups = init_groups_visitor ( $observer );
$gs = '<<>>' ; // should be impossible to match
if ( is_array ( $groups ) && count ( $groups )) {
foreach ( $groups as $g )
$gs .= '|<' . $g . '>' ;
}
$regexop = db_getfunc ( 'REGEXP' );
$sql = sprintf (
2016-03-20 08:06:33 +01:00
" AND ( NOT ( { $table } deny_cid like '%s' OR { $table } deny_gid $regexop '%s')
AND ( { $table } allow_cid like '%s' OR { $table } allow_gid $regexop '%s' OR ( { $table } allow_cid = '' AND { $table } allow_gid = '' ) )
2015-08-23 22:38:18 +02:00
)
" ,
dbesc ( protect_sprintf ( '%<' . $observer . '>%' )),
dbesc ( $gs ),
dbesc ( protect_sprintf ( '%<' . $observer . '>%' )),
dbesc ( $gs )
);
}
}
return $sql ;
}
/**
* @ brief Creates an addiontal SQL where statement to check permissions for an item .
*
* @ param int $owner_id
2016-01-12 02:47:38 +01:00
* @ param bool $remote_observer , use current observer if unset
2015-08-23 22:38:18 +02:00
*
* @ return string additional SQL where statement
*/
function item_permissions_sql ( $owner_id , $remote_observer = null ) {
$local_channel = local_channel ();
/**
* Construct permissions
*
* default permissions - anonymous user
*/
$sql = " AND item_private = 0 " ;
/**
* Profile owner - everything is visible
*/
if (( $local_channel ) && ( $local_channel == $owner_id )) {
$sql = '' ;
}
/**
* Authenticated visitor . Unless pre - verified ,
* check that the contact belongs to this $owner_id
* and load the groups the visitor belongs to .
* If pre - verified , the caller is expected to have already
* done this and passed the groups into this function .
*/
else {
$observer = (( $remote_observer ) ? $remote_observer : get_observer_hash ());
if ( $observer ) {
2015-10-24 13:04:14 +02:00
$s = scopes_sql ( $owner_id , $observer );
2015-08-23 22:38:18 +02:00
$groups = init_groups_visitor ( $observer );
$gs = '<<>>' ; // should be impossible to match
if ( is_array ( $groups ) && count ( $groups )) {
foreach ( $groups as $g )
$gs .= '|<' . $g . '>' ;
}
$regexop = db_getfunc ( 'REGEXP' );
$sql = sprintf (
2015-10-24 13:04:14 +02:00
" AND (( NOT (deny_cid like '%s' OR deny_gid $regexop '%s')
AND ( allow_cid like '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ))
) OR ( item_private = 1 $s ))
2015-08-23 22:38:18 +02:00
" ,
dbesc ( protect_sprintf ( '%<' . $observer . '>%' )),
dbesc ( $gs ),
dbesc ( protect_sprintf ( '%<' . $observer . '>%' )),
dbesc ( $gs )
);
}
}
return $sql ;
}
2015-10-24 13:04:14 +02:00
/**
* Remote visitors also need to be checked against the public_scope parameter if item_private is set .
* This function checks the various permutations of that field for any which apply to this observer .
*
*/
function scopes_sql ( $uid , $observer ) {
$str = " and ( public_policy = 'authenticated' " ;
if ( ! is_foreigner ( $observer ))
$str .= " or public_policy = 'network: red' " ;
if ( local_channel ())
2016-04-17 16:29:18 +02:00
$str .= " or public_policy = 'site: " . App :: get_hostname () . " ' " ;
2015-10-24 13:04:14 +02:00
$ab = q ( " select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1 " ,
dbesc ( $observer ),
intval ( $uid )
);
if ( ! $ab )
return $str . " ) " ;
if ( $ab [ 0 ][ 'abook_pending' ])
$str .= " or public_policy = 'any connections' " ;
$str .= " or public_policy = 'contacts' ) " ;
return $str ;
}
2015-08-23 22:38:18 +02:00
/**
* @ param string $observer_hash
*
* @ return string additional SQL where statement
*/
function public_permissions_sql ( $observer_hash ) {
2016-04-17 16:29:18 +02:00
//$observer = App::get_observer();
2015-08-23 22:38:18 +02:00
$groups = init_groups_visitor ( $observer_hash );
$gs = '<<>>' ; // should be impossible to match
if ( is_array ( $groups ) && count ( $groups )) {
foreach ( $groups as $g )
$gs .= '|<' . $g . '>' ;
}
$sql = '' ;
if ( $observer_hash ) {
$regexop = db_getfunc ( 'REGEXP' );
$sql = sprintf (
" OR (( NOT (deny_cid like '%s' OR deny_gid $regexop '%s')
2015-10-24 13:04:14 +02:00
AND ( allow_cid like '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ) )
2015-08-23 22:38:18 +02:00
))
" ,
dbesc ( protect_sprintf ( '%<' . $observer_hash . '>%' )),
dbesc ( $gs ),
dbesc ( protect_sprintf ( '%<' . $observer_hash . '>%' )),
dbesc ( $gs )
);
}
return $sql ;
}
/*
* Functions used to protect against Cross - Site Request Forgery
* The security token has to base on at least one value that an attacker can 't know - here it' s the session ID and the private key .
* In this implementation , a security token is reusable ( if the user submits a form , goes back and resubmits the form , maybe with small changes ;
* or if the security token is used for ajax - calls that happen several times ), but only valid for a certain amout of time ( 3 hours ) .
* The " typename " seperates the security tokens of different types of forms . This could be relevant in the following case :
* A security token is used to protekt a link from CSRF ( e . g . the " delete this profile " - link ) .
* If the new page contains by any chance external elements , then the used security token is exposed by the referrer .
* Actually , important actions should not be triggered by Links / GET - Requests at all , but somethimes they still are ,
* so this mechanism brings in some damage control ( the attacker would be able to forge a request to a form of this type , but not to forms of other types ) .
*/
function get_form_security_token ( $typename = '' ) {
$timestamp = time ();
2016-04-17 16:29:18 +02:00
$sec_hash = hash ( 'whirlpool' , App :: $observer [ 'xchan_guid' ] . (( local_channel ()) ? App :: $channel [ 'channel_prvkey' ] : '' ) . session_id () . $timestamp . $typename );
2015-08-23 22:38:18 +02:00
return $timestamp . '.' . $sec_hash ;
}
function check_form_security_token ( $typename = '' , $formname = 'form_security_token' ) {
if ( ! x ( $_REQUEST , $formname )) return false ;
$hash = $_REQUEST [ $formname ];
$max_livetime = 10800 ; // 3 hours
$x = explode ( '.' , $hash );
if ( time () > ( IntVal ( $x [ 0 ]) + $max_livetime )) return false ;
2016-04-17 16:29:18 +02:00
$sec_hash = hash ( 'whirlpool' , App :: $observer [ 'xchan_guid' ] . (( local_channel ()) ? App :: $channel [ 'channel_prvkey' ] : '' ) . session_id () . $x [ 0 ] . $typename );
2015-08-23 22:38:18 +02:00
return ( $sec_hash == $x [ 1 ]);
}
function check_form_security_std_err_msg () {
return t ( 'The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before submitting it.' ) . EOL ;
}
function check_form_security_token_redirectOnErr ( $err_redirect , $typename = '' , $formname = 'form_security_token' ) {
if ( ! check_form_security_token ( $typename , $formname )) {
2016-04-17 16:29:18 +02:00
logger ( 'check_form_security_token failed: user ' . App :: $observer [ 'xchan_name' ] . ' - form element ' . $typename );
2015-08-23 22:38:18 +02:00
logger ( 'check_form_security_token failed: _REQUEST data: ' . print_r ( $_REQUEST , true ), LOGGER_DATA );
notice ( check_form_security_std_err_msg () );
2016-04-17 16:29:18 +02:00
goaway ( z_root () . $err_redirect );
2015-08-23 22:38:18 +02:00
}
}
function check_form_security_token_ForbiddenOnErr ( $typename = '' , $formname = 'form_security_token' ) {
if ( ! check_form_security_token ( $typename , $formname )) {
2016-04-17 16:29:18 +02:00
logger ( 'check_form_security_token failed: user ' . App :: $observer [ 'xchan_name' ] . ' - form element ' . $typename );
2015-08-23 22:38:18 +02:00
logger ( 'check_form_security_token failed: _REQUEST data: ' . print_r ( $_REQUEST , true ), LOGGER_DATA );
header ( 'HTTP/1.1 403 Forbidden' );
killme ();
}
}
2016-01-12 02:47:38 +01:00
// Returns an array of group hash id's on this entire site (across all channels) that this connection is a member of.
// var $contact_id = xchan_hash of connection
2015-08-23 22:38:18 +02:00
function init_groups_visitor ( $contact_id ) {
$groups = array ();
$r = q ( " SELECT hash FROM `groups` left join group_member on groups.id = group_member.gid WHERE xchan = '%s' " ,
dbesc ( $contact_id )
);
2016-03-20 08:06:33 +01:00
if ( $r ) {
2015-08-23 22:38:18 +02:00
foreach ( $r as $rr )
$groups [] = $rr [ 'hash' ];
}
return $groups ;
2016-01-12 02:47:38 +01:00
}
2015-08-23 22:38:18 +02:00
// This is used to determine which uid have posts which are visible to the logged in user (from the API) for the
// public_timeline, and we can use this in a community page by making
// $perms = (PERMS_NETWORK|PERMS_PUBLIC) unless logged in.
// Collect uids of everybody on this site who has opened their posts to everybody on this site (or greater visibility)
// We always include yourself if logged in because you can always see your own posts
// resolving granular permissions for the observer against every person and every post on the site
// will likely be too expensive.
// Returns a string list of comma separated channel_ids suitable for direct inclusion in a SQL query
function stream_perms_api_uids ( $perms = NULL , $limit = 0 , $rand = 0 ) {
$perms = is_null ( $perms ) ? ( PERMS_SITE | PERMS_NETWORK | PERMS_PUBLIC ) : $perms ;
$ret = array ();
$limit_sql = (( $limit ) ? " LIMIT " . intval ( $limit ) . " " : '' );
$random_sql = (( $rand ) ? " ORDER BY " . db_getfunc ( 'RAND' ) . " " : '' );
if ( local_channel ())
$ret [] = local_channel ();
$r = q ( " select channel_id from channel where channel_r_stream > 0 and ( channel_r_stream & %d )>0 and ( channel_pageflags & %d ) = 0 and channel_system = 0 and channel_removed = 0 $random_sql $limit_sql " ,
intval ( $perms ),
intval ( PAGE_ADULT | PAGE_CENSORED )
);
if ( $r ) {
foreach ( $r as $rr )
if ( ! in_array ( $rr [ 'channel_id' ], $ret ))
$ret [] = $rr [ 'channel_id' ];
}
$str = '' ;
if ( $ret ) {
foreach ( $ret as $rr ) {
if ( $str )
$str .= ',' ;
$str .= intval ( $rr );
}
}
else
$str = " '' " ;
logger ( 'stream_perms_api_uids: ' . $str , LOGGER_DEBUG );
return $str ;
}
function stream_perms_xchans ( $perms = NULL ) {
$perms = is_null ( $perms ) ? ( PERMS_SITE | PERMS_NETWORK | PERMS_PUBLIC ) : $perms ;
$ret = array ();
if ( local_channel ())
$ret [] = get_observer_hash ();
$r = q ( " select channel_hash from channel where channel_r_stream > 0 and (channel_r_stream & %d)>0 and not (channel_pageflags & %d)>0 and channel_system = 0 and channel_removed = 0 " ,
intval ( $perms ),
intval ( PAGE_ADULT | PAGE_CENSORED )
);
if ( $r ) {
foreach ( $r as $rr )
if ( ! in_array ( $rr [ 'channel_hash' ], $ret ))
$ret [] = $rr [ 'channel_hash' ];
}
$str = '' ;
if ( $ret ) {
foreach ( $ret as $rr ) {
if ( $str )
$str .= ',' ;
$str .= " ' " . dbesc ( $rr ) . " ' " ;
}
}
else
$str = " '' " ;
logger ( 'stream_perms_xchans: ' . $str , LOGGER_DEBUG );
return $str ;
}