From 08b571c9d0843b19115bd9bb892cb141c508ea3b Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 14:30:02 -0800 Subject: [PATCH 01/17] project "snakebite" --- include/items.php | 50 ++++++++++++++++++++++++++++++++-- include/photo/photo_driver.php | 16 +++++++---- version.inc | 2 +- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/include/items.php b/include/items.php index c90bfb41c..7e15e9411 100755 --- a/include/items.php +++ b/include/items.php @@ -725,14 +725,60 @@ function import_author_xchan($x) { return $arr['xchan_hash']; if((! array_key_exists('network', $x)) || ($x['network'] === 'zot')) { - return import_author_zot($x); + $y = import_author_zot($x); } - // TODO: create xchans for other common and/or aligned networks + if($x['network'] === 'rss') { + $y = import_author_rss($x); + } + + return(($y) ? $y : false); +} + +function import_author_rss($x) { + + if(! $x['url']) + return false; + + $r = q("select xchan_hash from xchan where xchan_network = 'rss' and xchan_url = '%s' limit 1", + dbesc($x['url']) + ); + if($r) { + logger('import_author_rss: in cache' , LOGGER_DEBUG); + return $r[0]['xchan_hash']; + } + $name = trim($x['name']); + + $r = q("insert into xchan ( xchan_hash, xchan_url, xchan_name, xchan_network ) + values ( '%s', '%s', '%s', '%s' )", + dbesc($x['url']), + dbesc($x['url']), + dbesc(($name) ? $name : t('Unknown')), + dbesc('rss') + ); + if($r) { + + $photos = import_profile_photo($x['photo'],$x['url']); + + if($photos) { + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_url = '%s' and xchan_network = 'rss' limit 1", + dbesc(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($x['url']) + ); + if($r) + return $x['url']; + } + } return false; + } + function encode_item($item) { $x = array(); $x['type'] = 'activity'; diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index c2eeafa54..484550cb7 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -538,14 +538,20 @@ function import_profile_photo($photo,$xchan,$thing = false) { } $photo_failure = false; + $img_str = ''; + if($photo) { + $filename = basename($photo); + $type = guess_image_type($photo,true); - $filename = basename($photo); - $type = guess_image_type($photo,true); - $result = z_fetch_url($photo,true); + if(! $type) + $type = 'image/jpeg'; - if($result['success']) - $img_str = $result['body']; + $result = z_fetch_url($photo,true); + + if($result['success']) + $img_str = $result['body']; + } $img = photo_factory($img_str, $type); if($img->is_valid()) { diff --git a/version.inc b/version.inc index cc21a24b6..ee51fad76 100644 --- a/version.inc +++ b/version.inc @@ -1 +1 @@ -2014-02-16.590 +2014-02-17.591 From bee287f859e08b9033e83852f17d5bc52e22521b Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 18 Feb 2014 00:14:07 +0100 Subject: [PATCH 02/17] Commenting language.php and some changes Some commenting for Doxygen, simplified detect_language() a bit, added a new function get_language_name() that I will use soon or can be used in general to display localized language names from language codes. --- include/language.php | 173 ++++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 85 deletions(-) diff --git a/include/language.php b/include/language.php index 2e7ad5ff1..b43f5aacc 100644 --- a/include/language.php +++ b/include/language.php @@ -1,22 +1,28 @@ -strings = array(); load_translation_table($language); $a->language = $language; - } function pop_lang() { @@ -109,7 +123,7 @@ function load_translation_table($lang, $install = false) { if(! $install) { $plugins = q("SELECT name FROM addon WHERE installed=1;"); - if ($plugins!==false) { + if ($plugins !== false) { foreach($plugins as $p) { $name = $p['name']; if(file_exists("addon/$name/lang/$lang/strings.php")) { @@ -128,15 +142,18 @@ function load_translation_table($lang, $install = false) { } -// translate string if translation exists - +/** + * @brief translate string if translation exists. + * + * @param s string that should get translated + * @return translated string if exsists, otherwise s + */ function t($s) { - global $a; if(x($a->strings,$s)) { $t = $a->strings[$s]; - return is_array($t)?$t[0]:$t; + return is_array($t) ? $t[0] : $t; } return $s; } @@ -147,14 +164,14 @@ function tt($singular, $plural, $count){ if(x($a->strings,$singular)) { $t = $a->strings[$singular]; - $f = 'string_plural_select_' . str_replace('-','_',$a->language); + $f = 'string_plural_select_' . str_replace('-', '_', $a->language); if(! function_exists($f)) $f = 'string_plural_select_default'; $k = $f($count); - return is_array($t)?$t[$k]:$t; + return is_array($t) ? $t[$k] : $t; } - if ($count!=1){ + if ($count != 1){ return $plural; } else { return $singular; @@ -168,84 +185,47 @@ function string_plural_select_default($n) { return ($n != 1); } - - +/** + * @brief Takes a string and tries to identify the language. + * + * It uses the pear library Text_LanguageDetect and it can identify 52 human languages. + * It returns the identified languges and a confidence score for each. + * + * Strings need to have a min length config['system']['language_detect_min_length'] + * and you can influence the confidence that must be met before a result will get + * returned through config['system']['language_detect_min_confidence']. + * + * @see http://pear.php.net/package/Text_LanguageDetect + * @param s A string to examine + * @return Language code in 2-letter ISO 639-1 (en, de, fr) format + */ function detect_language($s) { - - $detected_languages = array( - 'Albanian' => 'sq', - 'Arabic' => 'ar', - 'Azeri' => 'az', - 'Bengali' => 'bn', - 'Bulgarian' => 'bg', - 'Cebuano' => '', - 'Croatian' => 'hr', - 'Czech' => 'cz', - 'Danish' => 'da', - 'Dutch' => 'nl', - 'English' => 'en', - 'Estonian' => 'et', - 'Farsi' => 'fa', - 'Finnish' => 'fi', - 'French' => 'fr', - 'German' => 'de', - 'Hausa' => 'ha', - 'Hawaiian' => '', - 'Hindi' => 'hi', - 'Hungarian' => 'hu', - 'Icelandic' => 'is', - 'Indonesian' => 'id', - 'Italian' => 'it', - 'Kazakh' => 'kk', - 'Kyrgyz' => 'ky', - 'Latin' => 'la', - 'Latvian' => 'lv', - 'Lithuanian' => 'lt', - 'Macedonian' => 'mk', - 'Mongolian' => 'mn', - 'Nepali' => 'ne', - 'Norwegian' => 'no', - 'Pashto' => 'ps', - 'Pidgin' => '', - 'Polish' => 'pl', - 'Portuguese' => 'pt', - 'Romanian' => 'ro', - 'Russian' => 'ru', - 'Serbian' => 'sr', - 'Slovak' => 'sk', - 'Slovene' => 'sl', - 'Somali' => 'so', - 'Spanish' => 'es', - 'Swahili' => 'sw', - 'Swedish' => 'sv', - 'Tagalog' => 'tl', - 'Turkish' => 'tr', - 'Ukrainian' => 'uk', - 'Urdu' => 'ur', - 'Uzbek' => 'uz', - 'Vietnamese' => 'vi', - 'Welsh' => 'cy' - ); - require_once('Text/LanguageDetect.php'); - $min_length = get_config('system','language_detect_min_length'); + $min_length = get_config('system', 'language_detect_min_length'); if($min_length === false) $min_length = LANGUAGE_DETECT_MIN_LENGTH; - $min_confidence = get_config('system','language_detect_min_confidence'); + $min_confidence = get_config('system', 'language_detect_min_confidence'); if($min_confidence === false) $min_confidence = LANGUAGE_DETECT_MIN_CONFIDENCE; - - $naked_body = preg_replace('/\[(.+?)\]/','',$s); - if(mb_strlen($naked_body) < intval($min_length)) + // strip off bbcode + $naked_body = preg_replace('/\[(.+?)\]/', '', $s); + if(mb_strlen($naked_body) < intval($min_length)) { + logger('detect language: string length less than ' . intval($min_length), LOGGER_DATA); return ''; + } $l = new Text_LanguageDetect; - $lng = $l->detectConfidence($naked_body); - - logger('detect language: ' . print_r($lng,true) . $naked_body, LOGGER_DATA); + try { + // return 2-letter ISO 639-1 (en) language code + $l->setNameMode(2); + $lng = $l->detectConfidence($naked_body); + logger('detect language: ' . print_r($lng, true) . $naked_body, LOGGER_DATA); + } catch (Text_LanguageDetect_Exception $e) { + logger('detect language exception: ' . $e->getMessage(), LOGGER_DATA); + } if((! $lng) || (! (x($lng,'language')))) { return ''; @@ -256,6 +236,29 @@ function detect_language($s) { return ''; } - return(($lng && (x($lng,'language'))) ? $detected_languages[ucfirst($lng['language'])] : ''); - + return($lng['language']); } + +/** + * @brief Returns the display name of a given language code. + * + * By default we use the localized language name. You can switch the result + * to any language with the optional 2nd parameter $l. + * + * $s and $l can be in any format that PHP's Locale understands. We will mostly + * use the 2-letter ISO 639-1 (en, de, fr) format. + * + * If nothing could be looked up it returns $s. + * + * @param $s Language code to look up + * @param $l (optional) In which language to return the name + * @return string with the language name, or $s if unrecognized + */ +function get_language_name($s, $l = null) { + if($l === null) + $l = $s; + + logger('get_language_name: for ' . $s . ' in ' . $l . ' returns: ' . Locale::getDisplayLanguage($s, $l), LOGGER_DEBUG); + return Locale::getDisplayLanguage($s, $l); +} + From b3ce1cd87b700073cab23eac4427671577e2f77e Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 16:00:17 -0800 Subject: [PATCH 03/17] project "snakebite" --- include/follow.php | 161 ++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 76 deletions(-) diff --git a/include/follow.php b/include/follow.php index 845ce11da..0508a8b37 100644 --- a/include/follow.php +++ b/include/follow.php @@ -16,6 +16,8 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) $result = array('success' => false,'message' => ''); $a = get_app(); + $is_red = false; + if(! allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); @@ -37,81 +39,93 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) $ret = zot_finger($url,$channel); if($ret['success']) { + $is_red = true; $j = json_decode($ret['body'],true); } - else { - $result['message'] = t('Channel discovery failed. Website may be down or misconfigured.'); - logger('mod_follow: ' . $result['message']); - return $result; - } - logger('follow: ' . $url . ' ' . print_r($j,true)); + if($is_red && $j) { - if(! $j) { - $result['message'] = t('Response from remote channel was not understood.'); - logger('mod_follow: ' . $result['message']); - return $result; - } + $my_perms = PERMS_W_STREAM|PERMS_W_MAIL; + + logger('follow: ' . $url . ' ' . print_r($j,true), LOGGER_DEBUG); - if(! ($j['success'] && $j['guid'])) { - $result['message'] = t('Response from remote channel was incomplete.'); - logger('mod_follow: ' . $result['message']); - return $result; - } - - // Premium channel, set confirm before callback to avoid recursion - - if(array_key_exists('connect_url',$j) && (! $confirm)) - goaway(zid($j['connect_url'])); - - - // check service class limits - - $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", - intval($uid), - intval(ABOOK_FLAG_SELF) - ); - if($r) - $total_channels = $r[0]['total']; - - if(! service_class_allows($uid,'total_channels',$total_channels)) { - $result['message'] = upgrade_message(); - return $result; - } - - // do we have an xchan and hubloc? - // If not, create them. - - $x = import_xchan($j); - - if(! $x['success']) - return $x; - - $xchan_hash = $x['hash']; - - - $their_perms = 0; - - $global_perms = get_perms(); - - if( array_key_exists('permissions',$j) && array_key_exists('data',$j['permissions'])) { - $permissions = crypto_unencapsulate(array( - 'data' => $j['permissions']['data'], - 'key' => $j['permissions']['key'], - 'iv' => $j['permissions']['iv']), - $channel['channel_prvkey']); - if($permissions) - $permissions = json_decode($permissions,true); - logger('decrypted permissions: ' . print_r($permissions,true), LOGGER_DATA); - } - else - $permissions = $j['permissions']; - - foreach($permissions as $k => $v) { - if($v) { - $their_perms = $their_perms | intval($global_perms[$k][1]); + if(! ($j['success'] && $j['guid'])) { + $result['message'] = t('Response from remote channel was incomplete.'); + logger('mod_follow: ' . $result['message']); + return $result; } + + // Premium channel, set confirm before callback to avoid recursion + + if(array_key_exists('connect_url',$j) && (! $confirm)) + goaway(zid($j['connect_url'])); + + // check service class limits + + $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", + intval($uid), + intval(ABOOK_FLAG_SELF) + ); + if($r) + $total_channels = $r[0]['total']; + + if(! service_class_allows($uid,'total_channels',$total_channels)) { + $result['message'] = upgrade_message(); + return $result; + } + + // do we have an xchan and hubloc? + // If not, create them. + + $x = import_xchan($j); + + if(! $x['success']) + return $x; + + $xchan_hash = $x['hash']; + + + $their_perms = 0; + + $global_perms = get_perms(); + + if( array_key_exists('permissions',$j) && array_key_exists('data',$j['permissions'])) { + $permissions = crypto_unencapsulate(array( + 'data' => $j['permissions']['data'], + 'key' => $j['permissions']['key'], + 'iv' => $j['permissions']['iv']), + $channel['channel_prvkey']); + if($permissions) + $permissions = json_decode($permissions,true); + logger('decrypted permissions: ' . print_r($permissions,true), LOGGER_DATA); + } + else + $permissions = $j['permissions']; + + foreach($permissions as $k => $v) { + if($v) { + $their_perms = $their_perms | intval($global_perms[$k][1]); + } + } + } + else { + + // attempt network auto-discovery + + $my_perms = 0; + $their_perms = 0; + $xchan_hash = ''; + + + + + } + + if(! $xchan_hash) { + $result['message'] = t('Channel discovery failed.'); + logger('follow: ' . $result['message']); + return $result; } if((local_user()) && $uid == local_user()) { @@ -156,7 +170,7 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) intval($uid), dbesc($xchan_hash), intval($their_perms), - intval(PERMS_W_STREAM|PERMS_W_MAIL), + intval($my_perms), dbesc(datetime_convert()), dbesc(datetime_convert()) ); @@ -172,7 +186,8 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) ); if($r) { $result['abook'] = $r[0]; - proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']); + if($is_red) + proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']); } $arr = array('channel_id' => $uid, 'abook' => $result['abook']); @@ -188,12 +203,6 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) group_add_member($uid,'',$xchan_hash,$g['id']); } - // Then send a ping/message to the other side - - $result['success'] = true; return $result; - - - } From d3120264cb99b8c87ecce01795bbc96265028b47 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 18:23:01 -0800 Subject: [PATCH 04/17] more snakebite stuff --- include/permissions.php | 13 +++++----- mod/rmagic.php | 54 ++++++++++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/include/permissions.php b/include/permissions.php index 420591c54..0cbb5b984 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -89,7 +89,7 @@ function get_all_perms($uid,$observer_xchan,$internal_use = true) { if($observer_xchan) { if(! $abook_checked) { - $x = q("select abook_my_perms, abook_flags from abook + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), dbesc($observer_xchan), @@ -137,9 +137,9 @@ function get_all_perms($uid,$observer_xchan,$internal_use = true) { continue; } - // If we're still here, we have an observer, which means they're in the network. + // If we're still here, we have an observer, check the network. - if($r[0][$channel_perm] & PERMS_NETWORK) { + if(($r[0][$channel_perm] & PERMS_NETWORK) && ($x[0]['xchan_network'] === 'zot')) { $ret[$perm_name] = true; continue; } @@ -240,7 +240,8 @@ function perm_is_allowed($uid,$observer_xchan,$permission) { return false; if($observer_xchan) { - $x = q("select abook_my_perms, abook_flags from abook where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash + where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), dbesc($observer_xchan), intval(ABOOK_FLAG_SELF) @@ -272,9 +273,9 @@ function perm_is_allowed($uid,$observer_xchan,$permission) { return false; } - // If we're still here, we have an observer, which means they're in the network. + // If we're still here, we have an observer, check the network. - if($r[0][$channel_perm] & PERMS_NETWORK) + if(($r[0][$channel_perm] & PERMS_NETWORK) && ($x[0]['xchan_network'] === 'zot')) return true; diff --git a/mod/rmagic.php b/mod/rmagic.php index b8c1c6553..093ccd328 100644 --- a/mod/rmagic.php +++ b/mod/rmagic.php @@ -22,31 +22,45 @@ function rmagic_init(&$a) { function rmagic_post(&$a) { - $address = $_REQUEST['address']; - if(strpos($address,'@') === false) { - notice('Invalid address.'); + $address = trim($_REQUEST['address']); + $other = intval($_REQUEST['other']); + + if($other) { + $arr = array('address' => $address); + call_hooks('reverse_magic_auth', $arr); + + + // if they're still here... + notice( t('Authentication failed.') . EOL); return; } - - $r = null; - if($address) { - $r = q("select hubloc_url from hubloc where hubloc_addr = '%s' limit 1", - dbesc($address) - ); - } - if($r) { - $url = $r[0]['hubloc_url']; - } else { - $url = 'https://' . substr($address,strpos($address,'@')+1); - } - if($url) { - $dest = z_root() . '/' . str_replace('zid=','zid_=',$a->query_string); - goaway($url . '/magic' . '?f=&dest=' . $dest); + // Presumed Red identity. Perform reverse magic auth + + if(strpos($address,'@') === false) { + notice('Invalid address.'); + return; + } + + $r = null; + if($address) { + $r = q("select hubloc_url from hubloc where hubloc_addr = '%s' limit 1", + dbesc($address) + ); + } + if($r) { + $url = $r[0]['hubloc_url']; + } + else { + $url = 'https://' . substr($address,strpos($address,'@')+1); + } + + if($url) { + $dest = z_root() . '/' . str_replace('zid=','zid_=',$a->query_string); + goaway($url . '/magic' . '?f=&dest=' . $dest); + } } - - } From 7fc292831cfc86cf818c3fb71596ef8acb01f689 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 18:50:24 -0800 Subject: [PATCH 05/17] update openid for snakebite --- library/openid/README | 49 ++ library/openid/example-google.php | 24 + library/openid/example.php | 23 + library/{ => openid}/openid.php | 260 ++++--- library/openid/provider/example-mysql.php | 194 +++++ library/openid/provider/example.php | 53 ++ library/openid/provider/provider.php | 845 ++++++++++++++++++++++ 7 files changed, 1346 insertions(+), 102 deletions(-) create mode 100644 library/openid/README create mode 100644 library/openid/example-google.php create mode 100644 library/openid/example.php rename library/{ => openid}/openid.php (74%) create mode 100644 library/openid/provider/example-mysql.php create mode 100644 library/openid/provider/example.php create mode 100644 library/openid/provider/provider.php diff --git a/library/openid/README b/library/openid/README new file mode 100644 index 000000000..799b452ac --- /dev/null +++ b/library/openid/README @@ -0,0 +1,49 @@ +This class provides a simple interface for OpenID (1.1 and 2.0) authentication. +Supports Yadis discovery. + +The authentication process is stateless/dumb. + +Usage: +Sign-on with OpenID is a two step process: +Step one is authentication with the provider: + +$openid = new LightOpenID('my-host.example.org'); +$openid->identity = 'ID supplied by user'; +header('Location: ' . $openid->authUrl()); + + +The provider then sends various parameters via GET, one of them is openid_mode. +Step two is verification: + +if ($this->data['openid_mode']) { + $openid = new LightOpenID('my-host.example.org'); + echo $openid->validate() ? 'Logged in.' : 'Failed'; +} + + * +Change the 'my-host.example.org' to your domain name. Do NOT use $_SERVER['HTTP_HOST'] +for that, unless you know what you are doing. + * +Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias). +The default values for those are: +$openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; +$openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; +If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess. + * +AX and SREG extensions are supported. +To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl(). +These are arrays, with values being AX schema paths (the 'path' part of the URL). +For example: + $openid->required = array('namePerson/friendly', 'contact/email'); + $openid->optional = array('namePerson/first'); +If the server supports only SREG or OpenID 1.1, these are automaticaly +mapped to SREG names, so that user doesn't have to know anything about the server. + * +To get the values, use $openid->getAttributes(). + * +The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled. +@author Mewp +@contributors Brice http://github.com/brice/ +@copyright Copyright (c) 2010, Mewp +@copyright Copyright (c) 2010, Brice +@license http://www.opensource.org/licenses/mit-license.php MIT \ No newline at end of file diff --git a/library/openid/example-google.php b/library/openid/example-google.php new file mode 100644 index 000000000..f23f2cc48 --- /dev/null +++ b/library/openid/example-google.php @@ -0,0 +1,24 @@ +mode) { + if(isset($_GET['login'])) { + $openid->identity = 'https://www.google.com/accounts/o8/id'; + header('Location: ' . $openid->authUrl()); + } +?> +
+ +
+mode == 'cancel') { + echo 'User has canceled authentication!'; + } else { + echo 'User ' . ($openid->validate() ? $openid->identity . ' has ' : 'has not ') . 'logged in.'; + } +} catch(ErrorException $e) { + echo $e->getMessage(); +} diff --git a/library/openid/example.php b/library/openid/example.php new file mode 100644 index 000000000..e4ab107fe --- /dev/null +++ b/library/openid/example.php @@ -0,0 +1,23 @@ +mode) { + if(isset($_POST['openid_identifier'])) { + $openid->identity = $_POST['openid_identifier']; + header('Location: ' . $openid->authUrl()); + } +?> +
+ OpenID: +
+mode == 'cancel') { + echo 'User has canceled authentication!'; + } else { + echo 'User ' . ($openid->validate() ? $openid->identity . ' has ' : 'has not ') . 'logged in.'; + } +} catch(ErrorException $e) { + echo $e->getMessage(); +} diff --git a/library/openid.php b/library/openid/openid.php similarity index 74% rename from library/openid.php rename to library/openid/openid.php index 3c58beb8a..00250c59d 100644 --- a/library/openid.php +++ b/library/openid/openid.php @@ -8,7 +8,7 @@ * Sign-on with OpenID is a two step process: * Step one is authentication with the provider: * - * $openid = new LightOpenID; + * $openid = new LightOpenID('my-host.example.org'); * $openid->identity = 'ID supplied by user'; * header('Location: ' . $openid->authUrl()); * @@ -16,15 +16,18 @@ * Step two is verification: * * if ($this->data['openid_mode']) { - * $openid = new LightOpenID; + * $openid = new LightOpenID('my-host.example.org'); * echo $openid->validate() ? 'Logged in.' : 'Failed'; * } * * + * Change the 'my-host.example.org' to your domain name. Do NOT use $_SERVER['HTTP_HOST'] + * for that, unless you know what you are doing. + * * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias). * The default values for those are: * $openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; - * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; # without the query part, if present + * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess. * * AX and SREG extensions are supported. @@ -39,7 +42,7 @@ * To get the values, use $openid->getAttributes(). * * - * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled.. + * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled. * @author Mewp * @copyright Copyright (c) 2010, Mewp * @license http://www.opensource.org/licenses/mit-license.php MIT @@ -49,11 +52,13 @@ class LightOpenID public $returnUrl , $required = array() , $optional = array() - , $verify_perr = null - , $capath = null; + , $verify_peer = null + , $capath = null + , $cainfo = null + , $data; private $identity, $claimed_id; protected $server, $version, $trustRoot, $aliases, $identifier_select = false - , $ax = false, $sreg = false, $data; + , $ax = false, $sreg = false, $setup_url = null; static protected $ax_to_sreg = array( 'namePerson/friendly' => 'nickname', 'contact/email' => 'email', @@ -66,14 +71,28 @@ class LightOpenID 'pref/timezone' => 'timezone', ); - function __construct() + function __construct($host) { - $this->trustRoot = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; - $uri = $_SERVER['REQUEST_URI']; - $uri = strpos($uri, '?') ? substr($uri, 0, strpos($uri, '?')) : $uri; + $this->trustRoot = (strpos($host, '://') ? $host : 'http://' . $host); + if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) + && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') + ) { + $this->trustRoot = (strpos($host, '://') ? $host : 'https://' . $host); + } + + if(($host_end = strpos($this->trustRoot, '/', 8)) !== false) { + $this->trustRoot = substr($this->trustRoot, 0, $host_end); + } + + $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); $this->returnUrl = $this->trustRoot . $uri; - $this->data = $_POST + $_GET; # OPs may send data as POST or GET. + $this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; + + if(!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { + throw new ErrorException('You must have either https wrappers or curl enabled.'); + } } function __set($name, $value) @@ -109,6 +128,8 @@ class LightOpenID case 'trustRoot': case 'realm': return $this->trustRoot; + case 'mode': + return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; } } @@ -143,11 +164,15 @@ class LightOpenID curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); - if($this->verify_perr !== null) { + if($this->verify_peer !== null) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); if($this->capath) { curl_setopt($curl, CURLOPT_CAPATH, $this->capath); } + + if($this->cainfo) { + curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); + } } if ($method == 'POST') { @@ -188,7 +213,7 @@ class LightOpenID protected function request_streams($url, $method='GET', $params=array()) { if(!$this->hostExists($url)) { - throw new ErrorException('Invalid request.'); + throw new ErrorException("Could not connect to $url.", 404); } $params = http_build_query($params, '', '&'); @@ -199,7 +224,9 @@ class LightOpenID 'method' => 'GET', 'header' => 'Accept: application/xrds+xml, */*', 'ignore_errors' => true, - ) + ), 'ssl' => array( + 'CN_match' => parse_url($url, PHP_URL_HOST), + ), ); $url = $url . ($params ? '?' . $params : ''); break; @@ -210,7 +237,9 @@ class LightOpenID 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $params, 'ignore_errors' => true, - ) + ), 'ssl' => array( + 'CN_match' => parse_url($url, PHP_URL_HOST), + ), ); break; case 'HEAD': @@ -219,11 +248,15 @@ class LightOpenID # we have to change the defaults. $default = stream_context_get_options(stream_context_get_default()); stream_context_get_default( - array('http' => array( - 'method' => 'HEAD', - 'header' => 'Accept: application/xrds+xml, */*', - 'ignore_errors' => true, - )) + array( + 'http' => array( + 'method' => 'HEAD', + 'header' => 'Accept: application/xrds+xml, */*', + 'ignore_errors' => true, + ), 'ssl' => array( + 'CN_match' => parse_url($url, PHP_URL_HOST), + ), + ) ); $url = $url . ($params ? '?' . $params : ''); @@ -263,10 +296,11 @@ class LightOpenID } if($this->verify_peer) { - $opts += array('ssl' => array( + $opts['ssl'] += array( 'verify_peer' => true, 'capath' => $this->capath, - )); + 'cafile' => $this->cainfo, + ); } $context = stream_context_create ($opts); @@ -276,7 +310,9 @@ class LightOpenID protected function request($url, $method='GET', $params=array()) { - if(function_exists('curl_init') && !ini_get('safe_mode') && (! strlen(ini_get('open_basedir')))) { + if (function_exists('curl_init') + && (!in_array('https', stream_get_wrappers()) || !ini_get('safe_mode') && !ini_get('open_basedir')) + ) { return $this->request_curl($url, $method, $params); } return $this->request_streams($url, $method, $params); @@ -297,7 +333,7 @@ class LightOpenID . (empty($url['port'])?'':":{$url['port']}") . (empty($url['path'])?'':$url['path']) . (empty($url['query'])?'':"?{$url['query']}") - . (empty($url['fragment'])?'':":{$url['fragment']}"); + . (empty($url['fragment'])?'':"#{$url['fragment']}"); return $url; } @@ -342,84 +378,90 @@ class LightOpenID $headers = $this->request($url, 'HEAD'); $next = false; - if (isset($headers['x-xrds-location'])) { - $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); - $next = true; - } + if (isset($headers['x-xrds-location'])) { + $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); + $next = true; + } - if (isset($headers['content-type']) - && ((strpos($headers['content-type'], 'application/xrds+xml') !== false - ) || (strpos($headers['content-type'], 'text/xml') !== false))) { - # Found an XRDS document, now let's find the server, and optionally delegate. - $content = $this->request($url, 'GET'); + if (isset($headers['content-type']) + && (strpos($headers['content-type'], 'application/xrds+xml') !== false + || strpos($headers['content-type'], 'text/xml') !== false) + ) { + # Apparently, some providers return XRDS documents as text/html. + # While it is against the spec, allowing this here shouldn't break + # compatibility with anything. + # --- + # Found an XRDS document, now let's find the server, and optionally delegate. + $content = $this->request($url, 'GET'); - preg_match_all('#(.*?)#s', $content, $m); - foreach($m[1] as $content) { - $content = ' ' . $content; # The space is added, so that strpos doesn't return 0. + preg_match_all('#(.*?)#s', $content, $m); + foreach($m[1] as $content) { + $content = ' ' . $content; # The space is added, so that strpos doesn't return 0. - # OpenID 2 - $ns = preg_quote('http://specs.openid.net/auth/2.0/'); - if(preg_match('#\s*'.$ns.'(server|signon)\s*#s', $content, $type)) { - if ($type[1] == 'server') $this->identifier_select = true; + # OpenID 2 + $ns = preg_quote('http://specs.openid.net/auth/2.0/'); + if(preg_match('#\s*'.$ns.'(server|signon)\s*#s', $content, $type)) { + if ($type[1] == 'server') $this->identifier_select = true; - preg_match('#(.*)#', $content, $server); - preg_match('#<(Local|Canonical)ID>(.*)#', $content, $delegate); - if (empty($server)) { - return false; - } - # Does the server advertise support for either AX or SREG? - $this->ax = (bool) strpos($content, 'http://openid.net/srv/ax/1.0'); - $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') - || strpos($content, 'http://openid.net/extensions/sreg/1.1'); - - $server = $server[1]; - if (isset($delegate[2])) $this->identity = trim($delegate[2]); - $this->version = 2; -logger('Server: ' . $server); - $this->server = $server; - return $server; + preg_match('#(.*)#', $content, $server); + preg_match('#<(Local|Canonical)ID>(.*)#', $content, $delegate); + if (empty($server)) { + return false; } + # Does the server advertise support for either AX or SREG? + $this->ax = (bool) strpos($content, 'http://openid.net/srv/ax/1.0'); + $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') + || strpos($content, 'http://openid.net/extensions/sreg/1.1'); - # OpenID 1.1 - $ns = preg_quote('http://openid.net/signon/1.1'); - if (preg_match('#\s*'.$ns.'\s*#s', $content)) { + $server = $server[1]; + if (isset($delegate[2])) $this->identity = trim($delegate[2]); + $this->version = 2; - preg_match('#(.*)#', $content, $server); - preg_match('#<.*?Delegate>(.*)#', $content, $delegate); - if (empty($server)) { - return false; - } - # AX can be used only with OpenID 2.0, so checking only SREG - $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') - || strpos($content, 'http://openid.net/extensions/sreg/1.1'); - - $server = $server[1]; - if (isset($delegate[1])) $this->identity = $delegate[1]; - $this->version = 1; - - $this->server = $server; - return $server; - } + $this->server = $server; + return $server; } - $next = true; - $yadis = false; - $url = $originalUrl; - $content = null; - break; + # OpenID 1.1 + $ns = preg_quote('http://openid.net/signon/1.1'); + if (preg_match('#\s*'.$ns.'\s*#s', $content)) { + + preg_match('#(.*)#', $content, $server); + preg_match('#<.*?Delegate>(.*)#', $content, $delegate); + if (empty($server)) { + return false; + } + # AX can be used only with OpenID 2.0, so checking only SREG + $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') + || strpos($content, 'http://openid.net/extensions/sreg/1.1'); + + $server = $server[1]; + if (isset($delegate[1])) $this->identity = $delegate[1]; + $this->version = 1; + + $this->server = $server; + return $server; + } } + + $next = true; + $yadis = false; + $url = $originalUrl; + $content = null; + break; + } if ($next) continue; # There are no relevant information in headers, so we search the body. $content = $this->request($url, 'GET'); - if ($location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content')) { + $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); + if ($location) { $url = $this->build_url(parse_url($url), parse_url($location)); continue; } } if (!$content) $content = $this->request($url, 'GET'); -logger('openid' . $content); + # At this point, the YADIS Discovery has failed, so we'll switch # to openid2 HTML discovery, then fallback to openid 1.1 discovery. $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); @@ -443,9 +485,9 @@ logger('openid' . $content); return $server; } - throw new ErrorException('No servers found!'); + throw new ErrorException("No OpenID Server found at $url", 404); } - throw new ErrorException('Endless redirection!'); + throw new ErrorException('Endless redirection!', 500); } protected function sregParams() @@ -514,7 +556,7 @@ logger('openid' . $content); return $params; } - protected function authUrl_v1() + protected function authUrl_v1($immediate) { $returnUrl = $this->returnUrl; # If we have an openid.delegate that is different from our claimed id, @@ -526,7 +568,7 @@ logger('openid' . $content); $params = array( 'openid.return_to' => $returnUrl, - 'openid.mode' => 'checkid_setup', + 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', 'openid.identity' => $this->identity, 'openid.trust_root' => $this->trustRoot, ) + $this->sregParams(); @@ -535,11 +577,11 @@ logger('openid' . $content); , array('query' => http_build_query($params, '', '&'))); } - protected function authUrl_v2($identifier_select) + protected function authUrl_v2($immediate) { $params = array( 'openid.ns' => 'http://specs.openid.net/auth/2.0', - 'openid.mode' => 'checkid_setup', + 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', 'openid.return_to' => $this->returnUrl, 'openid.realm' => $this->trustRoot, ); @@ -555,7 +597,7 @@ logger('openid' . $content); $params += $this->axParams() + $this->sregParams(); } - if ($identifier_select) { + if ($this->identifier_select) { $params['openid.identity'] = $params['openid.claimed_id'] = 'http://specs.openid.net/auth/2.0/identifier_select'; } else { @@ -573,17 +615,15 @@ logger('openid' . $content); * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1. * @throws ErrorException */ - function authUrl($identifier_select = null) + function authUrl($immediate = false) { + if ($this->setup_url && !$immediate) return $this->setup_url; if (!$this->server) $this->discover($this->identity); if ($this->version == 2) { - if ($identifier_select === null) { - return $this->authUrl_v2($this->identifier_select); - } - return $this->authUrl_v2($identifier_select); + return $this->authUrl_v2($immediate); } - return $this->authUrl_v1(); + return $this->authUrl_v1($immediate); } /** @@ -593,6 +633,18 @@ logger('openid' . $content); */ function validate() { + # If the request was using immediate mode, a failure may be reported + # by presenting user_setup_url (for 1.1) or reporting + # mode 'setup_needed' (for 2.0). Also catching all modes other than + # id_res, in order to avoid throwing errors. + if(isset($this->data['openid_user_setup_url'])) { + $this->setup_url = $this->data['openid_user_setup_url']; + return false; + } + if($this->mode != 'id_res') { + return false; + } + $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; $params = array( 'openid.assoc_handle' => $this->data['openid_assoc_handle'], @@ -605,7 +657,9 @@ logger('openid' . $content); # Even though we should know location of the endpoint, # we still need to verify it by discovery, so $server is not set here $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; - } elseif(isset($this->data['openid_claimed_id'])) { + } elseif (isset($this->data['openid_claimed_id']) + && $this->data['openid_claimed_id'] != $this->data['openid_identity'] + ) { # If it's an OpenID 1 provider, and we've got claimed_id, # we have to append it to the returnUrl, like authUrl_v1 does. $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') @@ -665,8 +719,8 @@ logger('openid' . $content); } $attributes = array(); - foreach ($this->data as $key => $value) { - $keyMatch = 'openid_' . $alias . '_value_'; + foreach (explode(',', $this->data['openid_signed']) as $key) { + $keyMatch = $alias . '.value.'; if (substr($key, 0, strlen($keyMatch)) != $keyMatch) { continue; } @@ -677,8 +731,10 @@ logger('openid' . $content); # to check, than cause an E_NOTICE. continue; } + $value = $this->data['openid_' . $alias . '_value_' . $key]; $key = substr($this->data['openid_' . $alias . '_type_' . $key], strlen('http://axschema.org/')); + $attributes[$key] = $value; } return $attributes; @@ -688,8 +744,8 @@ logger('openid' . $content); { $attributes = array(); $sreg_to_ax = array_flip(self::$ax_to_sreg); - foreach ($this->data as $key => $value) { - $keyMatch = 'openid_sreg_'; + foreach (explode(',', $this->data['openid_signed']) as $key) { + $keyMatch = 'sreg.'; if (substr($key, 0, strlen($keyMatch)) != $keyMatch) { continue; } @@ -698,7 +754,7 @@ logger('openid' . $content); # The field name isn't part of the SREG spec, so we ignore it. continue; } - $attributes[$sreg_to_ax[$key]] = $value; + $attributes[$sreg_to_ax[$key]] = $this->data['openid_sreg_' . $key]; } return $attributes; } diff --git a/library/openid/provider/example-mysql.php b/library/openid/provider/example-mysql.php new file mode 100644 index 000000000..574e3c811 --- /dev/null +++ b/library/openid/provider/example-mysql.php @@ -0,0 +1,194 @@ +dh = false; + * However, the latter one would disable stateful mode, unless connecting via HTTPS. + */ +require 'provider.php'; + +mysql_connect(); +mysql_select_db('test'); + +function getUserData($handle=null) +{ + if(isset($_POST['login'],$_POST['password'])) { + $login = mysql_real_escape_string($_POST['login']); + $password = sha1($_POST['password']); + $q = mysql_query("SELECT * FROM Users WHERE login = '$login' AND password = '$password'"); + if($data = mysql_fetch_assoc($q)) { + return $data; + } + if($handle) { + echo 'Wrong login/password.'; + } + } + if($handle) { + ?> +
+ + Login:
+ Password:
+ +
+ 'First name', + 'namePerson/last' => 'Last name', + 'namePerson/friendly' => 'Nickname (login)' + ); + + private $attrFieldMap = array( + 'namePerson/first' => 'firstName', + 'namePerson/last' => 'lastName', + 'namePerson/friendly' => 'login' + ); + + function setup($identity, $realm, $assoc_handle, $attributes) + { + $data = getUserData($assoc_handle); + echo '
' + . '' + . '' + . '' + . "$realm wishes to authenticate you."; + if($attributes['required'] || $attributes['optional']) { + echo " It also requests following information (required fields marked with *):" + . '
    '; + + foreach($attributes['required'] as $attr) { + if(isset($this->attrMap[$attr])) { + echo '
  • ' + . ' ' + . $this->attrMap[$attr] . '(*)
  • '; + } + } + + foreach($attributes['optional'] as $attr) { + if(isset($this->attrMap[$attr])) { + echo '
  • ' + . ' ' + . $this->attrMap[$attr] . '
  • '; + } + } + echo '
'; + } + echo '
' + . ' ' + . ' ' + . ' ' + . '
'; + } + + function checkid($realm, &$attributes) + { + if(isset($_POST['cancel'])) { + $this->cancel(); + } + + $data = getUserData(); + if(!$data) { + return false; + } + $realm = mysql_real_escape_string($realm); + $q = mysql_query("SELECT attributes FROM AllowedSites WHERE user = '{$data['id']}' AND realm = '$realm'"); + + $attrs = array(); + if($attrs = mysql_fetch_row($q)) { + $attrs = explode(',', $attributes[0]); + } elseif(isset($_POST['attributes'])) { + $attrs = array_keys($_POST['attributes']); + } elseif(!isset($_POST['once']) && !isset($_POST['always'])) { + return false; + } + + $attributes = array(); + foreach($attrs as $attr) { + if(isset($this->attrFieldMap[$attr])) { + $attributes[$attr] = $data[$this->attrFieldMap[$attr]]; + } + } + + if(isset($_POST['always'])) { + $attrs = mysql_real_escape_string(implode(',', array_keys($attributes))); + mysql_query("REPLACE INTO AllowedSites VALUES('{$data['id']}', '$realm', '$attrs')"); + } + + return $this->serverLocation . '?' . $data['login']; + } + + function assoc_handle() + { + # We generate an integer assoc handle, because it's just faster to look up an integer later. + $q = mysql_query("SELECT MAX(id) FROM Associations"); + $result = mysql_fetch_row($q); + return $q[0]+1; + } + + function setAssoc($handle, $data) + { + $data = mysql_real_escape_string(serialize($data)); + mysql_query("REPLACE INTO Associations VALUES('$handle', '$data')"); + } + + function getAssoc($handle) + { + if(!is_numeric($handle)) { + return false; + } + $q = mysql_query("SELECT data FROM Associations WHERE id = '$handle'"); + $data = mysql_fetch_row($q); + if(!$data) { + return false; + } + return unserialize($data[0]); + } + + function delAssoc($handle) + { + if(!is_numeric($handle)) { + return false; + } + mysql_query("DELETE FROM Associations WHERE id = '$handle'"); + } + +} +$op = new MysqlProvider; +$op->server(); diff --git a/library/openid/provider/example.php b/library/openid/provider/example.php new file mode 100644 index 000000000..b8a4c24a9 --- /dev/null +++ b/library/openid/provider/example.php @@ -0,0 +1,53 @@ +select_id = false; + } + } + + function setup($identity, $realm, $assoc_handle, $attributes) + { + header('WWW-Authenticate: Basic realm="' . $this->data['openid_realm'] . '"'); + header('HTTP/1.0 401 Unauthorized'); + } + + function checkid($realm, &$attributes) + { + if(!isset($_SERVER['PHP_AUTH_USER'])) { + return false; + } + + if ($_SERVER['PHP_AUTH_USER'] == $this->login + && $_SERVER['PHP_AUTH_PW'] == $this->password + ) { + # Returning identity + # It can be any url that leads here, or to any other place that hosts + # an XRDS document pointing here. + return $this->serverLocation . '?id=' . $this->login; + } + + return false; + } + +} +$op = new BasicProvider; +$op->login = 'test'; +$op->password = 'test'; +$op->server(); diff --git a/library/openid/provider/provider.php b/library/openid/provider/provider.php new file mode 100644 index 000000000..03fbe1c81 --- /dev/null +++ b/library/openid/provider/provider.php @@ -0,0 +1,845 @@ += 5.1.2 + * + * This is an alpha version, using it in production code is not recommended, + * until you are *sure* that it works and is secure. + * + * Please send me messages about your testing results + * (even if successful, so I know that it has been tested). + * Also, if you think there's a way to make it easier to use, tell me -- it's an alpha for a reason. + * Same thing applies to bugs in code, suggestions, + * and everything else you'd like to say about the library. + * + * There's no usage documentation here, see the examples. + * + * @author Mewp + * @copyright Copyright (c) 2010, Mewp + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ +ini_set('error_log','log'); +abstract class LightOpenIDProvider +{ + # URL-s to XRDS and server location. + public $xrdsLocation, $serverLocation; + + # Should we operate in server, or signon mode? + public $select_id = false; + + # Lifetime of an association. + protected $assoc_lifetime = 600; + + # Variables below are either set automatically, or are constant. + # ----- + # Can we support DH? + protected $dh = true; + protected $ns = 'http://specs.openid.net/auth/2.0'; + protected $data, $assoc; + + # Default DH parameters as defined in the specification. + protected $default_modulus; + protected $default_gen = 'Ag=='; + + # AX <-> SREG transform + protected $ax_to_sreg = array( + 'namePerson/friendly' => 'nickname', + 'contact/email' => 'email', + 'namePerson' => 'fullname', + 'birthDate' => 'dob', + 'person/gender' => 'gender', + 'contact/postalCode/home' => 'postcode', + 'contact/country/home' => 'country', + 'pref/language' => 'language', + 'pref/timezone' => 'timezone', + ); + + # Math + private $add, $mul, $pow, $mod, $div, $powmod; + # ----- + + # ------------------------------------------------------------------------ # + # Functions you probably want to implement when extending the class. + + /** + * Checks whether an user is authenticated. + * The function should determine what fields it wants to send to the RP, + * and put them in the $attributes array. + * @param Array $attributes + * @param String $realm Realm used for authentication. + * @return String OP-local identifier of an authenticated user, or an empty value. + */ + abstract function checkid($realm, &$attributes); + + /** + * Displays an user interface for inputting user's login and password. + * Attributes are always AX field namespaces, with stripped host part. + * For example, the $attributes array may be: + * array( 'required' => array('namePerson/friendly', 'contact/email'), + * 'optional' => array('pref/timezone', 'pref/language') + * @param String $identity Discovered identity string. May be used to extract login, unless using $this->select_id + * @param String $realm Realm used for authentication. + * @param String Association handle. must be sent as openid.assoc_handle in $_GET or $_POST in subsequent requests. + * @param Array User attributes requested by the RP. + */ + abstract function setup($identity, $realm, $assoc_handle, $attributes); + + /** + * Stores an association. + * If you want to use php sessions in your provider code, you have to replace it. + * @param String $handle Association handle -- should be used as a key. + * @param Array $assoc Association data. + */ + protected function setAssoc($handle, $assoc) + { + $oldSession = session_id(); + session_commit(); + session_id($assoc['handle']); + session_start(); + $_SESSION['assoc'] = $assoc; + session_commit(); + if($oldSession) { + session_id($oldSession); + session_start(); + } + } + + /** + * Retreives association data. + * If you want to use php sessions in your provider code, you have to replace it. + * @param String $handle Association handle. + * @return Array Association data. + */ + protected function getAssoc($handle) + { + $oldSession = session_id(); + session_commit(); + session_id($handle); + session_start(); + if(empty($_SESSION['assoc'])) { + return null; + } + return $_SESSION['assoc']; + session_commit(); + if($oldSession) { + session_id($oldSession); + session_start(); + } + } + + /** + * Deletes an association. + * If you want to use php sessions in your provider code, you have to replace it. + * @param String $handle Association handle. + */ + protected function delAssoc($handle) + { + $oldSession = session_id(); + session_commit(); + session_id($handle); + session_start(); + session_destroy(); + if($oldSession) { + session_id($oldSession); + session_start(); + } + } + + # ------------------------------------------------------------------------ # + # Functions that you might want to implement. + + /** + * Redirects the user to an url. + * @param String $location The url that the user will be redirected to. + */ + protected function redirect($location) + { + header('Location: ' . $location); + die(); + } + + /** + * Generates a new association handle. + * @return string + */ + protected function assoc_handle() + { + return sha1(microtime()); + } + + /** + * Generates a random shared secret. + * @return string + */ + protected function shared_secret($hash) + { + $length = 20; + if($hash == 'sha256') { + $length = 256; + } + + $secret = ''; + for($i = 0; $i < $length; $i++) { + $secret .= mt_rand(0,255); + } + + return $secret; + } + + /** + * Generates a private key. + * @param int $length Length of the key. + */ + protected function keygen($length) + { + $key = ''; + for($i = 1; $i < $length; $i++) { + $key .= mt_rand(0,9); + } + $key .= mt_rand(1,9); + + return $key; + } + + # ------------------------------------------------------------------------ # + # Functions that you probably shouldn't touch. + + function __construct() + { + $this->default_modulus = + 'ANz5OguIOXLsDhmYmsWizjEOHTdxfo2Vcbt2I3MYZuYe91ouJ4mLBX+YkcLiemOcPy' + . 'm2CBRYHNOyyjmG0mg3BVd9RcLn5S3IHHoXGHblzqdLFEi/368Ygo79JRnxTkXjgmY0' + . 'rxlJ5bU1zIKaSDuKdiI+XUkKJX8Fvf8W8vsixYOr'; + + $location = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' + . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $location = preg_replace('/\?.*/','',$location); + $this->serverLocation = $location; + $location .= (strpos($location, '?') ? '&' : '?') . 'xrds'; + $this->xrdsLocation = $location; + + $this->data = $_GET + $_POST; + + # We choose GMP if avaiable, and bcmath otherwise + if(function_exists('gmp_add')) { + $this->add = 'gmp_add'; + $this->mul = 'gmp_mul'; + $this->pow = 'gmp_pow'; + $this->mod = 'gmp_mod'; + $this->div = 'gmp_div'; + $this->powmod = 'gmp_powm'; + } elseif(function_exists('bcadd')) { + $this->add = 'bcadd'; + $this->mul = 'bcmul'; + $this->pow = 'bcpow'; + $this->mod = 'bcmod'; + $this->div = 'bcdiv'; + $this->powmod = 'bcpowmod'; + } else { + # If neither are avaiable, we can't use DH + $this->dh = false; + } + + # However, we do require the hash functions. + # They should be built-in anyway. + if(!function_exists('hash_algos')) { + $this->dh = false; + } + } + + /** + * Displays an XRDS document, or redirects to it. + * By default, it detects whether it should display or redirect automatically. + * @param bool|null $force When true, always display the document, when false always redirect. + */ + function xrds($force=null) + { + if($force) { + echo $this->xrdsContent(); + die(); + } elseif($force === false) { + header('X-XRDS-Location: '. $this->xrdsLocation); + return; + } + + if (isset($_GET['xrds']) + || (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xrds+xml') !== false) + ) { + header('Content-Type: application/xrds+xml'); + echo $this->xrdsContent(); + die(); + } + + header('X-XRDS-Location: ' . $this->xrdsLocation); + } + + /** + * Returns the content of the XRDS document + * @return String The XRDS document. + */ + protected function xrdsContent() + { + $lines = array( + '', + '', + '', + ' ', + ' ' . $this->ns . '/' . ($this->select_id ? 'server' : 'signon') .'', + ' ' . $this->serverLocation . '', + ' ', + '', + '' + ); + return implode("\n", $lines); + } + + /** + * Does everything that a provider has to -- in one function. + */ + function server() + { + if(isset($this->data['openid_assoc_handle'])) { + $this->assoc = $this->getAssoc($this->data['openid_assoc_handle']); + if(isset($this->assoc['data'])) { + # We have additional data stored for setup. + $this->data += $this->assoc['data']; + unset($this->assoc['data']); + } + } + + if (isset($this->data['openid_ns']) + && $this->data['openid_ns'] == $this->ns + ) { + if(!isset($this->data['openid_mode'])) $this->errorResponse(); + + switch($this->data['openid_mode']) + { + case 'checkid_immediate': + case 'checkid_setup': + $this->checkRealm(); + # We support AX xor SREG. + $attributes = $this->ax(); + if(!$attributes) { + $attributes = $this->sreg(); + } + + # Even if some user is authenticated, we need to know if it's + # the same one that want's to authenticate. + # Of course, if we use select_id, we accept any user. + if (($identity = $this->checkid($this->data['openid_realm'], $attrValues)) + && ($this->select_id || $identity == $this->data['openid_identity']) + ) { + $this->positiveResponse($identity, $attrValues); + } elseif($this->data['openid_mode'] == 'checkid_immediate') { + $this->redirect($this->response(array('openid.mode' => 'setup_needed'))); + } else { + if(!$this->assoc) { + $this->generateAssociation(); + $this->assoc['private'] = true; + } + $this->assoc['data'] = $this->data; + $this->setAssoc($this->assoc['handle'], $this->assoc); + $this->setup($this->data['openid_identity'], + $this->data['openid_realm'], + $this->assoc['handle'], + $attributes); + } + break; + case 'associate': + $this->associate(); + break; + case 'check_authentication': + $this->checkRealm(); + if($this->verify()) { + echo "ns:$this->ns\nis_valid:true"; + if(strpos($this->data['openid_signed'],'invalidate_handle') !== false) { + echo "\ninvalidate_handle:" . $this->data['openid_invalidate_handle']; + } + } else { + echo "ns:$this->ns\nis_valid:false"; + } + die(); + break; + default: + $this->errorResponse(); + } + } else { + $this->xrds(); + } + } + + protected function checkRealm() + { + if (!isset($this->data['openid_return_to'], $this->data['openid_realm'])) { + $this->errorResponse(); + } + + $realm = str_replace('\*', '[^/]', preg_quote($this->data['openid_realm'])); + if(!preg_match("#^$realm#", $this->data['openid_return_to'])) { + $this->errorResponse(); + } + } + + protected function ax() + { + # Namespace prefix that the fields must have. + $ns = 'http://axschema.org/'; + + # First, we must find out what alias is used for AX. + # Let's check the most likely one + $alias = null; + if (isset($this->data['openid_ns_ax']) + && $this->data['openid_ns_ax'] == 'http://openid.net/srv/ax/1.0' + ) { + $alias = 'ax'; + } else { + foreach($this->data as $name => $value) { + if ($value == 'http://openid.net/srv/ax/1.0' + && preg_match('/openid_ns_(.+)/', $name, $m) + ) { + $alias = $m[1]; + break; + } + } + } + + if(!$alias) { + return null; + } + + $fields = array(); + # Now, we must search again, this time for field aliases + foreach($this->data as $name => $value) { + if (strpos($name, 'openid_' . $alias . '_type') === false + || strpos($value, $ns) === false) { + continue; + } + + $name = substr($name, strlen('openid_' . $alias . '_type_')); + $value = substr($value, strlen($ns)); + + $fields[$name] = $value; + } + + # Then, we find out what fields are required and optional + $required = array(); + $if_available = array(); + foreach(array('required','if_available') as $type) { + if(empty($this->data["openid_{$alias}_{$type}"])) { + continue; + } + $attributes = explode(',', $this->data["openid_{$alias}_{$type}"]); + foreach($attributes as $attr) { + if(empty($fields[$attr])) { + # There is an undefined field here, so we ignore it. + continue; + } + + ${$type}[] = $fields[$attr]; + } + } + + $this->data['ax'] = true; + return array('required' => $required, 'optional' => $if_available); + } + + protected function sreg() + { + $sreg_to_ax = array_flip($this->ax_to_sreg); + + $attributes = array('required' => array(), 'optional' => array()); + + if (empty($this->data['openid_sreg_required']) + && empty($this->data['openid_sreg_optional']) + ) { + return $attributes; + } + + foreach(array('required', 'optional') as $type) { + foreach(explode(',',$this->data['openid_sreg_' . $type]) as $attr) { + if(empty($sreg_to_ax[$attr])) { + # Undefined attribute in SREG request. + # Shouldn't happen, but we check anyway. + continue; + } + + $attributes[$type][] = $sreg_to_ax[$attr]; + } + } + + return $attributes; + } + + /** + * Aids an RP in assertion verification. + * @return bool Information whether the verification suceeded. + */ + protected function verify() + { + # Firstly, we need to make sure that there's an association. + # Otherwise the verification will fail, + # because we've signed assoc_handle in the assertion + if(empty($this->assoc)) { + return false; + } + + # Next, we check that it's a private association, + # i.e. one made without RP input. + # Otherwise, the RP shouldn't ask us to verify. + if(empty($this->assoc['private'])) { + return false; + } + + # Now we have to check if the nonce is correct, to prevent replay attacks. + if($this->data['openid_response_nonce'] != $this->assoc['nonce']) { + return false; + } + + # Getting the signed fields for signature. + $sig = array(); + $signed = explode(',', $this->data['openid_signed']); + foreach($signed as $field) { + $name = strtr($field, '.', '_'); + if(!isset($this->data['openid_' . $name])) { + return false; + } + + $sig[$field] = $this->data['openid_' . $name]; + } + + # Computing the signature and checking if it matches. + $sig = $this->keyValueForm($sig); + if ($this->data['openid_sig'] != + base64_encode(hash_hmac($this->assoc['hash'], $sig, $this->assoc['mac'], true)) + ) { + return false; + } + + # Clearing the nonce, so that it won't be used again. + $this->assoc['nonce'] = null; + + if(empty($this->assoc['private'])) { + # Commiting changes to the association. + $this->setAssoc($this->assoc['handle'], $this->assoc); + } else { + # Private associations shouldn't be used again, se we can as well delete them. + $this->delAssoc($this->assoc['handle']); + } + + # Nothing has failed, so the verification was a success. + return true; + } + + /** + * Performs association with an RP. + */ + protected function associate() + { + # Rejecting no-encryption without TLS. + if(empty($_SERVER['HTTPS']) && $this->data['openid_session_type'] == 'no-encryption') { + $this->directErrorResponse(); + } + + # Checking whether we support DH at all. + if (!$this->dh && substr($this->data['openid_session_type'], 0, 2) == 'DH') { + $this->redirect($this->response(array( + 'openid.error' => 'DH not supported', + 'openid.error_code' => 'unsupported-type', + 'openid.session_type' => 'no-encryption' + ))); + } + + # Creating the association + $this->assoc = array(); + $this->assoc['hash'] = $this->data['openid_assoc_type'] == 'HMAC-SHA256' ? 'sha256' : 'sha1'; + $this->assoc['handle'] = $this->assoc_handle(); + + # Getting the shared secret + if($this->data['openid_session_type'] == 'no-encryption') { + $this->assoc['mac'] = base64_encode($this->shared_secret($this->assoc['hash'])); + } else { + $this->dh(); + } + + # Preparing the direct response... + $response = array( + 'ns' => $this->ns, + 'assoc_handle' => $this->assoc['handle'], + 'assoc_type' => $this->data['openid_assoc_type'], + 'session_type' => $this->data['openid_session_type'], + 'expires_in' => $this->assoc_lifetime + ); + + if(isset($this->assoc['dh_server_public'])) { + $response['dh_server_public'] = $this->assoc['dh_server_public']; + $response['enc_mac_key'] = $this->assoc['mac']; + } else { + $response['mac_key'] = $this->assoc['mac']; + } + + # ...and sending it. + echo $this->keyValueForm($response); + die(); + } + + /** + * Creates a private association. + */ + protected function generateAssociation() + { + $this->assoc = array(); + # We use sha1 by default. + $this->assoc['hash'] = 'sha1'; + $this->assoc['mac'] = $this->shared_secret('sha1'); + $this->assoc['handle'] = $this->assoc_handle(); + } + + /** + * Encrypts the MAC key using DH key exchange. + */ + protected function dh() + { + if(empty($this->data['openid_dh_modulus'])) { + $this->data['openid_dh_modulus'] = $this->default_modulus; + } + + if(empty($this->data['openid_dh_gen'])) { + $this->data['openid_dh_gen'] = $this->default_gen; + } + + if(empty($this->data['openid_dh_consumer_public'])) { + $this->directErrorResponse(); + } + + $modulus = $this->b64dec($this->data['openid_dh_modulus']); + $gen = $this->b64dec($this->data['openid_dh_gen']); + $consumerKey = $this->b64dec($this->data['openid_dh_consumer_public']); + + $privateKey = $this->keygen(strlen($modulus)); + $publicKey = $this->powmod($gen, $privateKey, $modulus); + $ss = $this->powmod($consumerKey, $privateKey, $modulus); + + $mac = $this->x_or(hash($this->assoc['hash'], $ss, true), $this->shared_secret($this->assoc['hash'])); + $this->assoc['dh_server_public'] = $this->decb64($publicKey); + $this->assoc['mac'] = base64_encode($mac); + } + + /** + * XORs two strings. + * @param String $a + * @param String $b + * @return String $a ^ $b + */ + protected function x_or($a, $b) + { + $length = strlen($a); + for($i = 0; $i < $length; $i++) { + $a[$i] = $a[$i] ^ $b[$i]; + } + + return $a; + } + + /** + * Prepares an indirect response url. + * @param array $params Parameters to be sent. + */ + protected function response($params) + { + $params += array('openid.ns' => $this->ns); + return $this->data['openid_return_to'] + . (strpos($this->data['openid_return_to'],'?') ? '&' : '?') + . http_build_query($params, '', '&'); + } + + /** + * Outputs a direct error. + */ + protected function errorResponse() + { + if(!empty($this->data['openid_return_to'])) { + $response = array( + 'openid.mode' => 'error', + 'openid.error' => 'Invalid request' + ); + $this->redirect($this->response($response)); + } else { + header('HTTP/1.1 400 Bad Request'); + $response = array( + 'ns' => $this->ns, + 'error' => 'Invalid request' + ); + echo $this->keyValueForm($response); + } + die(); + } + + /** + * Sends an positive assertion. + * @param String $identity the OP-Local Identifier that is being authenticated. + * @param Array $attributes User attributes to be sent. + */ + protected function positiveResponse($identity, $attributes) + { + # We generate a private association if there is none established. + if(!$this->assoc) { + $this->generateAssociation(); + $this->assoc['private'] = true; + } + + # We set openid.identity (and openid.claimed_id if necessary) to our $identity + if($this->data['openid_identity'] == $this->data['openid_claimed_id'] || $this->select_id) { + $this->data['openid_claimed_id'] = $identity; + } + $this->data['openid_identity'] = $identity; + + # Preparing fields to be signed + $params = array( + 'op_endpoint' => $this->serverLocation, + 'claimed_id' => $this->data['openid_claimed_id'], + 'identity' => $this->data['openid_identity'], + 'return_to' => $this->data['openid_return_to'], + 'realm' => $this->data['openid_realm'], + 'response_nonce' => gmdate("Y-m-d\TH:i:s\Z"), + 'assoc_handle' => $this->assoc['handle'], + ); + + $params += $this->responseAttributes($attributes); + + # Has the RP used an invalid association handle? + if (isset($this->data['openid_assoc_handle']) + && $this->data['openid_assoc_handle'] != $this->assoc['handle'] + ) { + $params['invalidate_handle'] = $this->data['openid_assoc_handle']; + } + + # Signing the $params + $sig = hash_hmac($this->assoc['hash'], $this->keyValueForm($params), $this->assoc['mac'], true); + $req = array( + 'openid.mode' => 'id_res', + 'openid.signed' => implode(',', array_keys($params)), + 'openid.sig' => base64_encode($sig), + ); + + # Saving the nonce and commiting the association. + $this->assoc['nonce'] = $params['response_nonce']; + $this->setAssoc($this->assoc['handle'], $this->assoc); + + # Preparing and sending the response itself + foreach($params as $name => $value) { + $req['openid.' . $name] = $value; + } + + $this->redirect($this->response($req)); + } + + /** + * Prepares an array of attributes to send + */ + protected function responseAttributes($attributes) + { + if(!$attributes) return array(); + + $ns = 'http://axschema.org/'; + + $response = array(); + if(isset($this->data['ax'])) { + $response['ns.ax'] = 'http://openid.net/srv/ax/1.0'; + foreach($attributes as $name => $value) { + $alias = strtr($name, '/', '_'); + $response['ax.type.' . $alias] = $ns . $name; + $response['ax.value.' . $alias] = $value; + } + return $response; + } + + foreach($attributes as $name => $value) { + if(!isset($this->ax_to_sreg[$name])) { + continue; + } + + $response['sreg.' . $this->ax_to_sreg[$name]] = $value; + } + return $response; + } + + /** + * Encodes fields in key-value form. + * @param Array $params Fields to be encoded. + * @return String $params in key-value form. + */ + protected function keyValueForm($params) + { + $str = ''; + foreach($params as $name => $value) { + $str .= "$name:$value\n"; + } + + return $str; + } + + /** + * Responds with an information that the user has canceled authentication. + */ + protected function cancel() + { + $this->redirect($this->response(array('openid.mode' => 'cancel'))); + } + + /** + * Converts base64 encoded number to it's decimal representation. + * @param String $str base64 encoded number. + * @return String Decimal representation of that number. + */ + protected function b64dec($str) + { + $bytes = unpack('C*', base64_decode($str)); + $n = 0; + foreach($bytes as $byte) { + $n = $this->add($this->mul($n, 256), $byte); + } + + return $n; + } + + /** + * Complements b64dec. + */ + protected function decb64($num) + { + $bytes = array(); + while($num) { + array_unshift($bytes, $this->mod($num, 256)); + $num = $this->div($num, 256); + } + + if($bytes && $bytes[0] > 127) { + array_unshift($bytes,0); + } + + array_unshift($bytes, 'C*'); + + return base64_encode(call_user_func_array('pack', $bytes)); + } + + function __call($name, $args) + { + switch($name) { + case 'add': + case 'mul': + case 'pow': + case 'mod': + case 'div': + case 'powmod': + if(function_exists('gmp_strval')) { + return gmp_strval(call_user_func_array($this->$name, $args)); + } + return call_user_func_array($this->$name, $args); + default: + throw new BadMethodCallException(); + } + } +} From d6ab975b188778a0be936c3065b502e0c58b8c91 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 19:48:05 -0800 Subject: [PATCH 06/17] operation snakebite continued. openid now works for local accounts using the rmagic module and after storing your openid in pconfig. This is just an interesting but trivial (in the bigger scheme of things) side effect of snakebite. The snake hasn't even waken up yet. --- include/auth.php | 10 +++++ include/text.php | 4 ++ mod/openid.php | 101 +++++++++++++++++++++++++++++++++++++++++++++++ mod/rmagic.php | 12 +++++- 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 mod/openid.php diff --git a/include/auth.php b/include/auth.php index 2b7c385fd..a4e859e0c 100644 --- a/include/auth.php +++ b/include/auth.php @@ -230,3 +230,13 @@ else { authenticate_success($record, true, true); } } + + +function match_openid($authid) { + $r = q("select * from pconfig where cat = 'system' and k = 'openid' "); + if($r) + foreach($r as $rr) + if($rr['v'] === $authid) + return $rr['uid']; + return false; +} diff --git a/include/text.php b/include/text.php index 2f5accf6e..2bf760035 100755 --- a/include/text.php +++ b/include/text.php @@ -1924,3 +1924,7 @@ function in_arrayi($needle, $haystack) { return in_array(strtolower($needle), array_map('strtolower', $haystack)); } +function normalise_openid($s) { + return trim(str_replace(array('http://','https://'),array('',''),$s),'/'); +} + diff --git a/mod/openid.php b/mod/openid.php new file mode 100644 index 000000000..d59d671e7 --- /dev/null +++ b/mod/openid.php @@ -0,0 +1,101 @@ +validate()) { + + logger('openid: validate'); + + $authid = normalise_openid($_REQUEST['openid_identity']); + + if(! strlen($authid)) { + logger( t('OpenID protocol error. No ID returned.') . EOL); + goaway(z_root()); + } + + $x = match_openid($authid); + if($x) { + + $r = q("select * from channel where channel_id = %d limit 1", + intval($x) + ); + if($r) { + $y = q("select * from account where account_id = %d limit 1", + intval($r[0]['channel_account_id']) + ); + if($y) { + foreach($y as $record) { + if(($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED)) { + logger('mod_openid: openid success for ' . $x[0]['channel_name']); + $_SESSION['uid'] = $r[0]['channel_id']; + $_SESSION['authenticated'] = true; + authenticate_success($record,true,true,true,true); + goaway(z_root()); + } + } + } + } + } + + // Successful OpenID login - but we can't match it to an existing account. + // New registration? + +// if($a->config['register_policy'] == REGISTER_CLOSED) { + notice( t('Account not found and OpenID registration is not permitted on this site.') . EOL); + goaway(z_root()); +// } + + unset($_SESSION['register']); + $args = ''; + $attr = $openid->getAttributes(); + if(is_array($attr) && count($attr)) { + foreach($attr as $k => $v) { + if($k === 'namePerson/friendly') + $nick = notags(trim($v)); + if($k === 'namePerson/first') + $first = notags(trim($v)); + if($k === 'namePerson') + $args .= '&username=' . notags(trim($v)); + if($k === 'contact/email') + $args .= '&email=' . notags(trim($v)); + if($k === 'media/image/aspect11') + $photosq = bin2hex(trim($v)); + if($k === 'media/image/default') + $photo = bin2hex(trim($v)); + } + } + if($nick) + $args .= '&nickname=' . $nick; + elseif($first) + $args .= '&nickname=' . $first; + + if($photosq) + $args .= '&photo=' . $photosq; + elseif($photo) + $args .= '&photo=' . $photo; + + $args .= '&openid_url=' . notags(trim($authid)); + + goaway($a->get_baseurl() . '/register' . $args); + + // NOTREACHED + } + } + notice( t('Login failed.') . EOL); + goaway(z_root()); + // NOTREACHED +} diff --git a/mod/rmagic.php b/mod/rmagic.php index 093ccd328..946277327 100644 --- a/mod/rmagic.php +++ b/mod/rmagic.php @@ -23,12 +23,20 @@ function rmagic_init(&$a) { function rmagic_post(&$a) { $address = trim($_REQUEST['address']); - $other = intval($_REQUEST['other']); - if($other) { + if(strpos($address,'@') === false) { $arr = array('address' => $address); call_hooks('reverse_magic_auth', $arr); + try { + require_once('library/openid/openid.php'); + $openid = new LightOpenID(z_root()); + $openid->identity = $address; + $openid->returnUrl = z_root() . '/openid'; + goaway($openid->authUrl()); + } catch (Exception $e) { + notice( t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.').'

'. t('The error message was:').' '.$e->getMessage()); + } // if they're still here... notice( t('Authentication failed.') . EOL); From ea709361f6a27f3234afbaeb6d3d1759eeca2ee5 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 20:10:43 -0800 Subject: [PATCH 07/17] snakebite, cont. magic-auth via openid is now possible, with the caveat that one needs a hand-crafted xchan at the moment to make use of it and if you wish to do so, there will be no assistance or help provided. The risk of system instability and mangled DBs now and going forward is significant if you try this. --- include/identity.php | 2 +- mod/openid.php | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/include/identity.php b/include/identity.php index d0fffaede..05a228abf 100644 --- a/include/identity.php +++ b/include/identity.php @@ -1137,7 +1137,7 @@ function get_default_profile_photo($size = 175) { */ function is_foreigner($s) { - return((strpbrk($s,':@')) ? true : false); + return((strpbrk($s,'.:@')) ? true : false); } diff --git a/mod/openid.php b/mod/openid.php index d59d671e7..42efeac70 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -59,8 +59,26 @@ function openid_content(&$a) { goaway(z_root()); // } - unset($_SESSION['register']); - $args = ''; + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($authid) + ); + + if($r) { + $_SESSION['authenticated'] = 1; + $_SESSION['visitor_id'] = $r[0]['xchan_hash']; + $_SESSION['my_address'] = $r[0]['xchan_addr']; + $arr = array('xchan' => $r[0], 'session' => $_SESSION); + call_hooks('magic_auth_openid_success',$arr); + $a->set_observer($r[0]); + require_once('include/security.php'); + $a->set_groups(init_groups_visitor($_SESSION['visitor_id'])); + info(sprintf( t('Welcome %s. Remote authentication successful.'),$r[0]['xchan_name'])); + logger('mod_openid: remote auth success from ' . $r[0]['xchan_addr']); + if($_SESSION['return_url']) + goaway($_SESSION['return_url']); + goaway(z_root()); + } + $attr = $openid->getAttributes(); if(is_array($attr) && count($attr)) { foreach($attr as $k => $v) { From d5c55250f96e4fc16afc08ae910ac75fa9b43fa6 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 20:24:34 -0800 Subject: [PATCH 08/17] remove the exit clause --- mod/openid.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mod/openid.php b/mod/openid.php index 42efeac70..aae8b17d3 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -52,12 +52,6 @@ function openid_content(&$a) { } // Successful OpenID login - but we can't match it to an existing account. - // New registration? - -// if($a->config['register_policy'] == REGISTER_CLOSED) { - notice( t('Account not found and OpenID registration is not permitted on this site.') . EOL); - goaway(z_root()); -// } $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($authid) From 309ae2d1e4d54e4940cd619ae4937f710a75b1ca Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 20:33:52 -0800 Subject: [PATCH 09/17] update the donation link --- README.md | 2 +- mod/siteinfo.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 93dad882b..419793e19 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The Red Matrix is free and open source distributed under the MIT license. Please connect with one of the developer channels ("Channel One" would be a good choice) if you are interested in helping us out. -[Please help us change the world by providing a small donation.](http://pledgie.com/campaigns/18417) (Large donations are also graciously accepted). +[Please help us change the world by providing a small donation.](http://redmatrix.me/siteinfo) (Large donations are also graciously accepted). If you would like to become a member of the Red Matrix **right now** , please select a public hub from one of our open providers at [https://zothub.com/pubsites](https://zothub.com/pubsites). All sites are interlinked and you can always move to another, so the choice of site can be somewhat arbitrary. \ No newline at end of file diff --git a/mod/siteinfo.php b/mod/siteinfo.php index 6b962c488..7fdb892d2 100644 --- a/mod/siteinfo.php +++ b/mod/siteinfo.php @@ -91,7 +91,7 @@ function siteinfo_content(&$a) { $admininfo = bbcode(get_config('system','admininfo')); $project_donate = t('Project Donations'); - $donate_text = t('

The Red Matrix is provided for you by volunteers working in their spare time. Your support will help us to build a better web. Select the following option for a one-time donation of your choosing

'); + $donate_text = t('

The Red Matrix is provided for you by volunteers working in their spare time. Your support will help us to build a better, freer, and privacy respecting web. Select the following option for a one-time donation of your choosing

'); $alternatively = t('

or

'); $recurring = t('Recurring Donation Options'); @@ -99,12 +99,12 @@ function siteinfo_content(&$a) {

{$project_donate}

$donate_text

-$alternatively +$alternatively

-
$recurring

+

EOT; From 0b4aa5a39c6cef862b4fbcbc71771f4931a4bc68 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 17 Feb 2014 20:38:30 -0800 Subject: [PATCH 10/17] get_theme_uid() - if no uid, look for a system channel and use that. This allows default theme/scheme tweaks at the server level. --- include/identity.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/identity.php b/include/identity.php index 05a228abf..d83498a69 100644 --- a/include/identity.php +++ b/include/identity.php @@ -1104,6 +1104,11 @@ function get_theme_uid() { if(! $uid) return local_user(); } + if(! $uid) { + $x = get_sys_channel(); + if($x) + return $x['channel_id']; + } return $uid; } From aa44d3abab07205a747874b244e71181e4e7a1b0 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 18 Feb 2014 17:41:59 +0100 Subject: [PATCH 11/17] Let user cancel the red_encrypt dialogue If the user press cancel on the prompt also cancel the encryption. Maybe should also add this to passhint promt. --- view/js/crypto.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/view/js/crypto.js b/view/js/crypto.js index 2e6402c62..c3a37d177 100644 --- a/view/js/crypto.js +++ b/view/js/crypto.js @@ -1,5 +1,4 @@ - function str_rot13 (str) { // http://kevin.vanzonneveld.net // + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) @@ -43,7 +42,11 @@ function red_encrypt(alg, elem,text) { // key and hint need to be localised - var enc_key = bin2hex(prompt(aStr['passphrase'])); + var passphrase = prompt(aStr['passphrase']); + // let the user cancel this dialogue + if (passphrase == null) + return false; + var enc_key = bin2hex(passphrase); // If you don't provide a key you get rot13, which doesn't need a key // but consequently isn't secure. From 9a51f8ce650d295e7bc2322b9f2f10b340d8e076 Mon Sep 17 00:00:00 2001 From: friendica Date: Tue, 18 Feb 2014 14:26:23 -0800 Subject: [PATCH 12/17] edit bookmarks --- include/menu.php | 2 +- mod/bookmarks.php | 4 ++-- mod/openid.php | 2 +- version.inc | 2 +- view/css/mod_mitem.css | 7 +++++++ view/tpl/mitemlist.tpl | 5 +++-- view/tpl/usermenu.tpl | 3 +++ 7 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 view/css/mod_mitem.css diff --git a/include/menu.php b/include/menu.php index e9049bf8e..2f1719d0b 100644 --- a/include/menu.php +++ b/include/menu.php @@ -38,7 +38,7 @@ function menu_render($menu, $edit = false) { return replace_macros(get_markup_template('usermenu.tpl'),array( '$menu' => $menu['menu'], - '$edit' => $edit, + '$edit' => (($edit) ? t("Edit") : ''), '$items' => $menu['items'] )); } diff --git a/mod/bookmarks.php b/mod/bookmarks.php index 67208937d..c5be68b8e 100644 --- a/mod/bookmarks.php +++ b/mod/bookmarks.php @@ -57,7 +57,7 @@ function bookmarks_content(&$a) { if($x) { foreach($x as $xx) { $y = menu_fetch($xx['menu_name'],local_user(),get_observer_hash()); - $o .= menu_render($y); + $o .= menu_render($y,true); } } @@ -69,7 +69,7 @@ function bookmarks_content(&$a) { if($x) { foreach($x as $xx) { $y = menu_fetch($xx['menu_name'],local_user(),get_observer_hash()); - $o .= menu_render($y); + $o .= menu_render($y,true); } } diff --git a/mod/openid.php b/mod/openid.php index aae8b17d3..6f97c6fb9 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -6,7 +6,7 @@ require_once('include/auth.php'); function openid_content(&$a) { - $noid = get_config('system','no_openid'); + $noid = get_config('system','disable_openid'); if($noid) goaway(z_root()); diff --git a/version.inc b/version.inc index ee51fad76..4551fa398 100644 --- a/version.inc +++ b/version.inc @@ -1 +1 @@ -2014-02-17.591 +2014-02-18.592 diff --git a/view/css/mod_mitem.css b/view/css/mod_mitem.css new file mode 100644 index 000000000..377d164fe --- /dev/null +++ b/view/css/mod_mitem.css @@ -0,0 +1,7 @@ +.menu-item-list { + list-style-type: none; +} + +.mitem-edit { + margin-right: 15px; +} \ No newline at end of file diff --git a/view/tpl/mitemlist.tpl b/view/tpl/mitemlist.tpl index 057665d49..421b610f1 100644 --- a/view/tpl/mitemlist.tpl +++ b/view/tpl/mitemlist.tpl @@ -4,12 +4,13 @@ {{$edmenu}}
{{$hintnew}} +

{{if $mlist }} -
    + {{/if}} diff --git a/view/tpl/usermenu.tpl b/view/tpl/usermenu.tpl index 3904f4696..80e160fdf 100644 --- a/view/tpl/usermenu.tpl +++ b/view/tpl/usermenu.tpl @@ -2,6 +2,9 @@ {{if $menu.menu_desc}}

    {{$menu.menu_desc}}

    {{/if}} +{{if $edit}} + +{{/if}} {{if $items }}
      {{foreach $items as $mitem }} From 7d4916ec714d834db3493c2fc12e76b0ffdb26b2 Mon Sep 17 00:00:00 2001 From: friendica Date: Tue, 18 Feb 2014 15:17:18 -0800 Subject: [PATCH 13/17] service class downgrade to the default service class on account expiration if using a non-default service class and account has expired. --- include/account.php | 52 +++++++++++++++++++++++++++++++++++++++++++++ include/poller.php | 13 +++--------- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/include/account.php b/include/account.php index 7d1aa598d..242e6512f 100644 --- a/include/account.php +++ b/include/account.php @@ -401,3 +401,55 @@ function user_deny($hash) { return true; } + + +/** + * @function downgrade_accounts() + * Checks for accounts that have past their expiration date. + * If the account has a service class which is not the site default, + * the service class is reset to the site default and expiration reset to never. + * If the account has no service class it is expired and subsequently disabled. + * called from include/poller.php as a scheduled task. + * + * Reclaiming resources which are no longer within the service class limits is + * not the job of this function, but this can be implemented by plugin if desired. + * Default behaviour is to stop allowing additional resources to be consumed. + */ + + +function downgrade_accounts() { + + $r = q("select * from account where not ( account_flags & %d ) + and account_expires != '0000-00-00 00:00:00' + and account_expires < UTC_TIMESTAMP() ", + intval(ACCOUNT_EXPIRED) + ); + + if(! $r) + return; + + $basic = get_config('system','default_service_class'); + + + foreach($r as $rr) { + + if(($basic) && ($rr['account_service_class']) && ($rr['account_service_class'] != $basic)) { + $x = q("UPDATE account set account_service_class = '%s', account_expires = '%s' + where account_id = %d limit 1", + dbesc($basic), + dbesc('0000-00-00 00:00:00'), + intval($rr['account_id']) + ); + call_hooks('account_downgrade', array('account' => $rr)); + logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' downgraded.'); + } + else { + $x = q("UPDATE account SET account_flags = (account_flags | %d) where account_id = %d limit 1", + intval(ACCOUNT_EXPIRED), + intval($rr['account_id']) + ); + call_hooks('account_downgrade', array('account' => $rr)); + logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' expired.'); + } + } +} \ No newline at end of file diff --git a/include/poller.php b/include/poller.php index ce9b75eb3..1c6f68eab 100644 --- a/include/poller.php +++ b/include/poller.php @@ -32,16 +32,6 @@ function poller_run($argv, $argc){ proc_run('php',"include/queue.php"); - // expire any expired accounts - - q("UPDATE account - SET account_flags = (account_flags | %d) - where not (account_flags & %d) - and account_expires != '0000-00-00 00:00:00' - and account_expires < UTC_TIMESTAMP() ", - intval(ACCOUNT_EXPIRED), - intval(ACCOUNT_EXPIRED) - ); // expire any expired mail @@ -115,6 +105,9 @@ function poller_run($argv, $argc){ q("delete from notify where seen = 1 and date < UTC_TIMESTAMP() - INTERVAL 30 DAY"); + // expire any expired accounts + require_once('include/account.php'); + downgrade_accounts(); // If this is a directory server, request a sync with an upstream // directory at least once a day, up to once every poll interval. From 5747e20e502cd4504aef4371b30631265579e81c Mon Sep 17 00:00:00 2001 From: friendica Date: Tue, 18 Feb 2014 16:59:31 -0800 Subject: [PATCH 14/17] some more snakebite and fix up include/account - forgot about that inline array stuff --- include/account.php | 9 +++++--- include/auth.php | 8 +++---- mod/item.php | 10 +++++++++ mod/openid.php | 51 ++++++++++++++++++++++++++++++++------------- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/include/account.php b/include/account.php index 242e6512f..1206223d9 100644 --- a/include/account.php +++ b/include/account.php @@ -440,7 +440,8 @@ function downgrade_accounts() { dbesc('0000-00-00 00:00:00'), intval($rr['account_id']) ); - call_hooks('account_downgrade', array('account' => $rr)); + $ret = array('account' => $rr); + call_hooks('account_downgrade', $ret ); logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' downgraded.'); } else { @@ -448,8 +449,10 @@ function downgrade_accounts() { intval(ACCOUNT_EXPIRED), intval($rr['account_id']) ); - call_hooks('account_downgrade', array('account' => $rr)); + $ret = array('account' => $rr); + call_hooks('account_downgrade', $ret); logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' expired.'); } } -} \ No newline at end of file +} + diff --git a/include/auth.php b/include/auth.php index a4e859e0c..425715014 100644 --- a/include/auth.php +++ b/include/auth.php @@ -233,10 +233,10 @@ else { function match_openid($authid) { - $r = q("select * from pconfig where cat = 'system' and k = 'openid' "); + $r = q("select * from pconfig where cat = 'system' and k = 'openid' and v = '%s' limit 1", + dbesc($authid) + ); if($r) - foreach($r as $rr) - if($rr['v'] === $authid) - return $rr['uid']; + return $r[0]['uid']; return false; } diff --git a/mod/item.php b/mod/item.php index fa7720791..dc005bb20 100644 --- a/mod/item.php +++ b/mod/item.php @@ -453,6 +453,16 @@ function item_post(&$a) { * the post and we should keep it private. If it's encrypted we have no way of knowing * so we'll set the permissions regardless and realise that the media may not be * referenced in the post. + * + * What is preventing us from being able to upload photos into comments is dealing with + * the photo and attachment permissions, since we don't always know who was in the + * distribution for the top level post. + * + * We might be able to provide this functionality with a lot of fiddling: + * - if the top level post is public (make the photo public) + * - if the top level post was written by us or a wall post that belongs to us (match the top level post) + * - if the top level post has privacy mentions, add those to the permissions. + * - otherwise disallow the photo *or* make the photo public. This is the part that gets messy. */ if(! $preview) { diff --git a/mod/openid.php b/mod/openid.php index 6f97c6fb9..e1c71f9ee 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -52,8 +52,9 @@ function openid_content(&$a) { } // Successful OpenID login - but we can't match it to an existing account. + // See if they've got an xchan - $r = q("select * from xchan where xchan_hash = '%s' limit 1", + $r = q("select * from xconfig left join xchan on xchan_hash = xconfig.xchan where cat = 'system' and k = 'openid' and v = '%s' limit 1", dbesc($authid) ); @@ -73,7 +74,22 @@ function openid_content(&$a) { goaway(z_root()); } + // no xchan... + // create one. + // We should probably probe the openid url. + + $name = $authid; + $url = $_REQUEST['openid_identity']; + if(strpos($url,'http') === false) + $url = 'https://' . $url; + $pphoto = get_default_profile_photo(); + $parsed = @parse_url($url); + if($parsed) { + $host = $parsed['host']; + } + $attr = $openid->getAttributes(); + if(is_array($attr) && count($attr)) { foreach($attr as $k => $v) { if($k === 'namePerson/friendly') @@ -81,30 +97,37 @@ function openid_content(&$a) { if($k === 'namePerson/first') $first = notags(trim($v)); if($k === 'namePerson') - $args .= '&username=' . notags(trim($v)); + $name = notags(trim($v)); if($k === 'contact/email') - $args .= '&email=' . notags(trim($v)); + $addr = notags(trim($v)); if($k === 'media/image/aspect11') - $photosq = bin2hex(trim($v)); + $photosq = trim($v); if($k === 'media/image/default') - $photo = bin2hex(trim($v)); + $photo_other = trim($v); } } - if($nick) - $args .= '&nickname=' . $nick; - elseif($first) - $args .= '&nickname=' . $first; + if(! $nick) { + if($first) + $nick = $first; + else + $nick = $name; + } + require_once('library/urlify/URLify.php'); + $x = strtolower(URLify::transliterate($nick)); + if(! $addr) + $addr = $nick . '@' . $host; + $network = 'unknown'; + if($photosq) - $args .= '&photo=' . $photosq; + $pphoto = $photosq; elseif($photo) - $args .= '&photo=' . $photo; + $pphoto = $photo; - $args .= '&openid_url=' . notags(trim($authid)); - - goaway($a->get_baseurl() . '/register' . $args); + // add the xchan record and xconfig for the openid // NOTREACHED + // actually it is reached until the other bits get written } } notice( t('Login failed.') . EOL); From 87bb568d678c88efacc5d849af7a474e8efc9359 Mon Sep 17 00:00:00 2001 From: friendica Date: Tue, 18 Feb 2014 19:17:11 -0800 Subject: [PATCH 15/17] change edit/delete to icons on filestorage page --- view/tpl/filestorage.tpl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/view/tpl/filestorage.tpl b/view/tpl/filestorage.tpl index 7b88c6440..1995b95e1 100644 --- a/view/tpl/filestorage.tpl +++ b/view/tpl/filestorage.tpl @@ -2,13 +2,14 @@
      {{if $limit}}{{$limitlabel}}{{$limit}}{{/if}} {{if $used}} {{$usedlabel}}{{$used}}{{/if}} - +
      +
      {{foreach $files as $key => $items}} {{foreach $items as $item}}
      - {{$edit}}   | - {{$delete}} |    +      +      {{if ! $item.dir}}{{/if}}{{$item.title}}{{if ! $item.dir}}{{/if}} {{if ! $item.dir}} | {{$item.size}} bytes{{else}}{{$directory}}{{/if}} From 6ac81c936077caa7167ddc3a2eb3c1022826d5d9 Mon Sep 17 00:00:00 2001 From: friendica Date: Tue, 18 Feb 2014 19:47:13 -0800 Subject: [PATCH 16/17] fix zrl bookmarks which broke with this checkin: c9192991c95a5145 It was documented that: Issues: Currently the order of HTML parameters in the text is somewhat rigid and inflexible. but as that was in a different function it was easy to overlook. --- include/text.php | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/include/text.php b/include/text.php index 2bf760035..dfd35c769 100755 --- a/include/text.php +++ b/include/text.php @@ -1324,24 +1324,15 @@ function prepare_text($text,$content_type = 'text/bbcode') { function zidify_callback($match) { - if (feature_enabled(local_user(),'sendzid')) { - $replace = ' Date: Tue, 18 Feb 2014 20:59:25 -0800 Subject: [PATCH 17/17] introduce a new privacy level "PERMS_AUTHED" to indicate somebody that is able to successfully authenticate (but is not necessarily in this network). --- boot.php | 1 + include/auth.php | 2 +- include/permissions.php | 8 +++++ mod/openid.php | 68 ++++++++++++++++++++++++++++++++++++----- mod/settings.php | 3 +- view/js/mod_settings.js | 24 +++++++-------- 6 files changed, 84 insertions(+), 22 deletions(-) diff --git a/boot.php b/boot.php index b875014bd..1d8ec2143 100755 --- a/boot.php +++ b/boot.php @@ -279,6 +279,7 @@ define ( 'PERMS_NETWORK' , 0x0002 ); define ( 'PERMS_SITE' , 0x0004 ); define ( 'PERMS_CONTACTS' , 0x0008 ); define ( 'PERMS_SPECIFIC' , 0x0080 ); +define ( 'PERMS_AUTHED' , 0x0100 ); // Address book flags diff --git a/include/auth.php b/include/auth.php index 425715014..a3b028c73 100644 --- a/include/auth.php +++ b/include/auth.php @@ -93,7 +93,7 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p } } - $r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_hash = '%s' limit 1", + $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s' limit 1", dbesc($_SESSION['visitor_id']) ); if($r) { diff --git a/include/permissions.php b/include/permissions.php index 0cbb5b984..eb1a7966f 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -88,6 +88,11 @@ function get_all_perms($uid,$observer_xchan,$internal_use = true) { // These take priority over all other settings. if($observer_xchan) { + if($r[0][$channel_perm] & PERMS_AUTHED) { + $ret[$perm_name] = true; + continue; + } + if(! $abook_checked) { $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", @@ -240,6 +245,9 @@ function perm_is_allowed($uid,$observer_xchan,$permission) { return false; if($observer_xchan) { + if($r[0][$channel_perm] & PERMS_AUTHED) + return true; + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), diff --git a/mod/openid.php b/mod/openid.php index e1c71f9ee..1ab8749ee 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -76,10 +76,11 @@ function openid_content(&$a) { // no xchan... // create one. - // We should probably probe the openid url. + // We should probably probe the openid url and figure out if they have any kind of social presence we might be able to + // scrape some identifying info from. $name = $authid; - $url = $_REQUEST['openid_identity']; + $url = trim($_REQUEST['openid_identity'],'/'); if(strpos($url,'http') === false) $url = 'https://' . $url; $pphoto = get_default_profile_photo(); @@ -115,19 +116,70 @@ function openid_content(&$a) { require_once('library/urlify/URLify.php'); $x = strtolower(URLify::transliterate($nick)); - if(! $addr) + if($nick & $host) $addr = $nick . '@' . $host; $network = 'unknown'; if($photosq) $pphoto = $photosq; - elseif($photo) - $pphoto = $photo; + elseif($photo_other) + $pphoto = $photo_other; - // add the xchan record and xconfig for the openid + $x = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_mimetype, + xchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_follow, xchan_connpage, xchan_name, xchan_network, xchan_photo_date, + xchan_name_date, xchan_flags) + values ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d) ", + dbesc($url), + dbesc(''), + dbesc(''), + dbesc(''), + dbesc('image/jpeg'), + dbesc($pphoto), + dbesc($addr), + dbesc($url), + dbesc(''), + dbesc(''), + dbesc(''), + dbesc($name), + dbesc($network), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval(XCHAN_FLAGS_HIDDEN) + ); + if($x) { + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($url) + ); + if($r) { + + $photos = import_profile_photo($pphoto,$url); + if($photos) { + $z = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', + xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s' limit 1", + dbesc(datetime_convert()), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($url) + ); + } + + set_xconfig($url,'system','openid',$authid); + $_SESSION['authenticated'] = 1; + $_SESSION['visitor_id'] = $r[0]['xchan_hash']; + $_SESSION['my_address'] = $r[0]['xchan_addr']; + $arr = array('xchan' => $r[0], 'session' => $_SESSION); + call_hooks('magic_auth_openid_success',$arr); + $a->set_observer($r[0]); + info(sprintf( t('Welcome %s. Remote authentication successful.'),$r[0]['xchan_name'])); + logger('mod_openid: remote auth success from ' . $r[0]['xchan_addr']); + if($_SESSION['return_url']) + goaway($_SESSION['return_url']); + goaway(z_root()); + } + } - // NOTREACHED - // actually it is reached until the other bits get written } } notice( t('Login failed.') . EOL); diff --git a/mod/settings.php b/mod/settings.php index 97965d0fd..5b0a8e8f2 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -798,6 +798,7 @@ function settings_content(&$a) { array( t('Anybody in your address book'), PERMS_CONTACTS), array( t('Anybody on this website'), PERMS_SITE), array( t('Anybody in this network'), PERMS_NETWORK), + array( t('Anybody authenticated'), PERMS_AUTHED), array( t('Anybody on the internet'), PERMS_PUBLIC) ); @@ -979,7 +980,7 @@ function settings_content(&$a) { '$h_descadvn' => t('Change the behaviour of this account for special situations'), '$pagetype' => $pagetype, '$expert' => feature_enabled(local_user(),'expert'), - '$hint' => t('Please enable expert mode (in Settings > Additional features) to adjust!'), + '$hint' => t('Please enable expert mode (in Settings > Additional features) to adjust!'), )); diff --git a/view/js/mod_settings.js b/view/js/mod_settings.js index 16101db57..8cd062f43 100644 --- a/view/js/mod_settings.js +++ b/view/js/mod_settings.js @@ -72,12 +72,12 @@ function channel_privacy_macro(n) { $('#id_profile_in_directory').val(0); } if(n == 2) { - $('#id_view_stream option').eq(5).attr('selected','selected'); - $('#id_view_profile option').eq(5).attr('selected','selected'); - $('#id_view_photos option').eq(5).attr('selected','selected'); - $('#id_view_contacts option').eq(5).attr('selected','selected'); - $('#id_view_storage option').eq(5).attr('selected','selected'); - $('#id_view_pages option').eq(5).attr('selected','selected'); + $('#id_view_stream option').eq(6).attr('selected','selected'); + $('#id_view_profile option').eq(6).attr('selected','selected'); + $('#id_view_photos option').eq(6).attr('selected','selected'); + $('#id_view_contacts option').eq(6).attr('selected','selected'); + $('#id_view_storage option').eq(6).attr('selected','selected'); + $('#id_view_pages option').eq(6).attr('selected','selected'); $('#id_send_stream option').eq(2).attr('selected','selected'); $('#id_post_wall option').eq(1).attr('selected','selected'); $('#id_post_comments option').eq(2).attr('selected','selected'); @@ -95,12 +95,12 @@ function channel_privacy_macro(n) { $('#id_profile_in_directory').val(1); } if(n == 3) { - $('#id_view_stream option').eq(5).attr('selected','selected'); - $('#id_view_profile option').eq(5).attr('selected','selected'); - $('#id_view_photos option').eq(5).attr('selected','selected'); - $('#id_view_contacts option').eq(5).attr('selected','selected'); - $('#id_view_storage option').eq(5).attr('selected','selected'); - $('#id_view_pages option').eq(5).attr('selected','selected'); + $('#id_view_stream option').eq(6).attr('selected','selected'); + $('#id_view_profile option').eq(6).attr('selected','selected'); + $('#id_view_photos option').eq(6).attr('selected','selected'); + $('#id_view_contacts option').eq(6).attr('selected','selected'); + $('#id_view_storage option').eq(6).attr('selected','selected'); + $('#id_view_pages option').eq(6).attr('selected','selected'); $('#id_send_stream option').eq(4).attr('selected','selected'); $('#id_post_wall option').eq(4).attr('selected','selected'); $('#id_post_comments option').eq(4).attr('selected','selected');